From af95df35340f9071936b457cb0fb8321a1f43253 Mon Sep 17 00:00:00 2001 From: mcgurn Date: Sat, 23 Nov 2019 14:21:48 -0700 Subject: [PATCH 01/23] code cleanup and testing of cache and configuration --- .golangci.json | 20 ++ README.md | 10 + cache/ObjectMemCache.go | 104 ------ cache/{ObjectCache.go => cache.go} | 4 +- cache/memory.go | 66 ++++ cache/memory_test.go | 162 +++++++++ cache/{ObjectRedisCache.go => redis.go} | 48 +-- configuration/Configuration.go | 292 ---------------- configuration/configuration.go | 22 ++ configuration/json.go | 261 ++++++++++++++ configuration/json_test.go | 432 ++++++++++++++++++++++++ configuration/{SQL.go => sql.go} | 32 +- configuration/sql_test.go | 63 ++++ go.mod | 13 + go.sum | 59 ++++ google/Sheets.go | 2 +- middleware/JwtHandler.go | 2 +- mocks/mock_configuration.go | 219 ++++++++++++ mocks/mock_memory.go | 60 ++++ passwords/BasicHelper.go | 2 +- passwords/ResetRepoSql.go | 4 +- preferences/Handlers.go | 2 +- preferences/Option.go | 2 +- preferences/RepoSql.go | 2 +- roles/PermissionTableJson.go | 2 +- roles/RepoSql.go | 2 +- static/Handlers.go | 2 +- static/RepoCache.go | 4 +- stl/Creator.go | 77 +++-- stl/Edge.go | 11 +- stl/Element.go | 17 +- stl/Extrude.go | 13 +- stl/Mesh.go | 2 +- stl/Rotation.go | 2 +- stl/TriLine.go | 42 ++- stl/Vertex.go | 18 +- users/Facebook.go | 6 +- users/Google.go | 6 +- users/Helper.go | 2 +- 39 files changed, 1525 insertions(+), 564 deletions(-) create mode 100644 .golangci.json delete mode 100644 cache/ObjectMemCache.go rename cache/{ObjectCache.go => cache.go} (82%) create mode 100644 cache/memory.go create mode 100644 cache/memory_test.go rename cache/{ObjectRedisCache.go => redis.go} (58%) delete mode 100644 configuration/Configuration.go create mode 100644 configuration/configuration.go create mode 100644 configuration/json.go create mode 100644 configuration/json_test.go rename configuration/{SQL.go => sql.go} (52%) create mode 100644 configuration/sql_test.go create mode 100644 mocks/mock_configuration.go create mode 100644 mocks/mock_memory.go 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..7afa770 100644 --- a/README.md +++ b/README.md @@ -13,3 +13,13 @@ 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 \ 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 82% rename from cache/ObjectCache.go rename to cache/cache.go index 5fe8973..51ed087 100644 --- a/cache/ObjectCache.go +++ b/cache/cache.go @@ -3,8 +3,8 @@ package cache -type ObjectCache interface { - Get(key string, item interface{}) +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..ff39b27 --- /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 TestSet(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 TestSetString(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 TestGet(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 TestGetString(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..1575c05 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,45 @@ 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{}) { //Get the summary err := repo.codec.Get(key, &item) if err != nil { item = nil } - } -/** -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..281ed4c --- /dev/null +++ b/configuration/json.go @@ -0,0 +1,261 @@ +// 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 NewJson(configFiles ...string) (*Json, error) { + //Define a Configuration + config := Json{ + params: make(map[string]interface{}, 0), + fatal: func(i interface{}) { + log.Fatal(i) + }, + } + + // 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 +} + +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) *Json { + //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..6fba398 --- /dev/null +++ b/configuration/json_test.go @@ -0,0 +1,432 @@ +// 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 TestGet(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 TestGetFatal(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 TestGetString(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 TestGetError(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 TestGetStringFatal(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 TestGetInt(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 TestGetIntFatal(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 TestGetFloat(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 TestGetKeys(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 TestGetConfig(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 *Json + if len(testCase.expectedConfig) > 0 { + expected, _ = NewJson(testCase.expectedConfig) + } + if result == nil { + assert.Equal(t, expected, result) + } else { + assert.Equal(t, expected.params, result.params) + } + } +} + +func TestGetStruct(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 TestGetStringArray(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 TestGetBool(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..3c1b641 --- /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 TestGetMySqlDataBaseSourceName(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 TestGetPostgresDataBaseSourceName(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/go.mod b/go.mod index 33d047a..b938ed2 100644 --- a/go.mod +++ b/go.mod @@ -8,18 +8,31 @@ require ( github.com/domodwyer/mailyak v3.1.1+incompatible github.com/fogleman/fauxgl v0.0.0-20190627205746-5ab08979c242 github.com/fogleman/simplify v0.0.0-20170216171241-d32f302d5046 // 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.4.1 github.com/go-stack/stack v1.8.0 // indirect + github.com/golang/mock v1.3.1 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/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/onsi/ginkgo v1.10.3 // indirect + github.com/onsi/gomega v1.7.1 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pkg/errors v0.8.1 // indirect + github.com/stretchr/testify v1.4.0 + github.com/tidwall/pretty v1.0.0 // indirect + github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // 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/net v0.0.0-20190620200207-3b0461eec859 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 + golang.org/x/tools v0.0.0-20191122000530-004141db30c2 // indirect google.golang.org/api v0.7.0 ) diff --git a/go.sum b/go.sum index 9c96a81..46b1daa 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +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/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= @@ -14,13 +16,23 @@ github.com/fogleman/fauxgl v0.0.0-20190627205746-5ab08979c242 h1:MZ1TQZgNq5gVCn+ github.com/fogleman/fauxgl v0.0.0-20190627205746-5ab08979c242/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/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/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.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 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/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/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/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= @@ -28,6 +40,7 @@ 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/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/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -39,13 +52,35 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA 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/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +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/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/onsi/ginkgo v1.6.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.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +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/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 v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0= @@ -63,15 +98,21 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk 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-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 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-20190108225652-1e06a53dbb7e/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-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-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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= @@ -83,8 +124,10 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ 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/sys v0.0.0-20180830151530-49385e6e1522/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/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/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= @@ -96,12 +139,18 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm 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-20190312170243-e65039ee4138/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-20191122000530-004141db30c2 h1:NyRLhayH0+K/d7asTjF9pRtOfq1G7nNsKRP3XzA3lVc= +golang.org/x/tools v0.0.0-20191122000530-004141db30c2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -111,6 +160,16 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +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/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +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/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= 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= diff --git a/google/Sheets.go b/google/Sheets.go index a47e00b..29e398e 100644 --- a/google/Sheets.go +++ b/google/Sheets.go @@ -4,8 +4,8 @@ package google import ( - "github.com/reaction-eng/restlib/configuration" "errors" + "github.com/reaction-eng/restlib/configuration" "golang.org/x/net/context" "golang.org/x/oauth2/google" "golang.org/x/oauth2/jwt" diff --git a/middleware/JwtHandler.go b/middleware/JwtHandler.go index 462d875..e7d858f 100644 --- a/middleware/JwtHandler.go +++ b/middleware/JwtHandler.go @@ -4,12 +4,12 @@ package middleware import ( + "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" 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_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/passwords/BasicHelper.go b/passwords/BasicHelper.go index fe1ae4d..3e05073 100644 --- a/passwords/BasicHelper.go +++ b/passwords/BasicHelper.go @@ -10,8 +10,8 @@ import ( "log" "strings" - "github.com/reaction-eng/restlib/configuration" "github.com/dgrijalva/jwt-go" + "github.com/reaction-eng/restlib/configuration" "golang.org/x/crypto/bcrypt" ) diff --git a/passwords/ResetRepoSql.go b/passwords/ResetRepoSql.go index 9eac84c..a3fa5de 100644 --- a/passwords/ResetRepoSql.go +++ b/passwords/ResetRepoSql.go @@ -4,10 +4,10 @@ package passwords import ( - "github.com/reaction-eng/restlib/configuration" - "github.com/reaction-eng/restlib/email" "database/sql" "errors" + "github.com/reaction-eng/restlib/configuration" + "github.com/reaction-eng/restlib/email" "log" "time" ) diff --git a/preferences/Handlers.go b/preferences/Handlers.go index 3f4f9ea..94c63a3 100644 --- a/preferences/Handlers.go +++ b/preferences/Handlers.go @@ -4,10 +4,10 @@ package preferences import ( + "encoding/json" "github.com/reaction-eng/restlib/routing" "github.com/reaction-eng/restlib/users" "github.com/reaction-eng/restlib/utils" - "encoding/json" "io/ioutil" "net/http" ) diff --git a/preferences/Option.go b/preferences/Option.go index 41a3ab2..b39105b 100644 --- a/preferences/Option.go +++ b/preferences/Option.go @@ -27,7 +27,7 @@ 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) configFileStream.Close() diff --git a/preferences/RepoSql.go b/preferences/RepoSql.go index 834c604..dfa07f0 100644 --- a/preferences/RepoSql.go +++ b/preferences/RepoSql.go @@ -4,8 +4,8 @@ package preferences import ( - "github.com/reaction-eng/restlib/users" "database/sql" + "github.com/reaction-eng/restlib/users" "log" ) diff --git a/roles/PermissionTableJson.go b/roles/PermissionTableJson.go index ec67279..595701a 100644 --- a/roles/PermissionTableJson.go +++ b/roles/PermissionTableJson.go @@ -36,7 +36,7 @@ func NewPermissionTableJson(fileName string) *PermissionTableJson { if err != nil { log.Fatal(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) diff --git a/roles/RepoSql.go b/roles/RepoSql.go index 19a3ada..98de884 100644 --- a/roles/RepoSql.go +++ b/roles/RepoSql.go @@ -4,8 +4,8 @@ package roles import ( - "github.com/reaction-eng/restlib/users" "database/sql" + "github.com/reaction-eng/restlib/users" "log" ) 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/RepoCache.go b/static/RepoCache.go index f208181..c9ab945 100644 --- a/static/RepoCache.go +++ b/static/RepoCache.go @@ -14,7 +14,7 @@ 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 @@ -25,7 +25,7 @@ type RepoCache struct { } //Provide a method to make a new AnimalRepoSql -func NewRepoCache(drive *google.Drive, cas cache.ObjectCache, privateConfigFile string, publicConfigFile string) *RepoCache { +func NewRepoCache(drive *google.Drive, cas cache.Cache, privateConfigFile string, publicConfigFile string) *RepoCache { //Create a new config privateConfig, _ := configuration.NewConfiguration(privateConfigFile) 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 Date: Sun, 24 Nov 2019 12:56:35 -0700 Subject: [PATCH 02/23] testing and mocks for email and configuration --- cache/memory_test.go | 8 +- configuration/json_test.go | 26 +- configuration/sql_test.go | 4 +- email/Interface.go | 56 --- email/emailer.go | 39 ++ email/mailyak.go | 33 ++ email/smtpConnection.go | 24 ++ email/{SmtpSender.go => smtpEmailer.go} | 158 ++++---- email/smtpEmailer_test.go | 458 ++++++++++++++++++++++++ file/{Storage.go => storage.go} | 2 + mocks/mock_emailer.go | 170 +++++++++ mocks/mock_smtpConnection.go | 192 ++++++++++ mocks/mock_storage.go | 64 ++++ utils/Base64File.go | 8 +- 14 files changed, 1080 insertions(+), 162 deletions(-) delete mode 100644 email/Interface.go create mode 100644 email/emailer.go create mode 100644 email/mailyak.go create mode 100644 email/smtpConnection.go rename email/{SmtpSender.go => smtpEmailer.go} (65%) create mode 100644 email/smtpEmailer_test.go rename file/{Storage.go => storage.go} (62%) create mode 100644 mocks/mock_emailer.go create mode 100644 mocks/mock_smtpConnection.go create mode 100644 mocks/mock_storage.go diff --git a/cache/memory_test.go b/cache/memory_test.go index ff39b27..010bb18 100644 --- a/cache/memory_test.go +++ b/cache/memory_test.go @@ -27,7 +27,7 @@ func TestNewMemory(t *testing.T) { assert.Equal(t, mockMemoryCache, memory.rawMemoryCache) } -func TestSet(t *testing.T) { +func TestMemory_Set(t *testing.T) { testCases := []struct { key string item interface{} @@ -54,7 +54,7 @@ func TestSet(t *testing.T) { mockCtrl.Finish() } -func TestSetString(t *testing.T) { +func TestMemory_SetString(t *testing.T) { testCases := []struct { key string item string @@ -79,7 +79,7 @@ func TestSetString(t *testing.T) { mockCtrl.Finish() } -func TestGet(t *testing.T) { +func TestMemory_Get(t *testing.T) { testCases := []struct { key string get interface{} @@ -130,7 +130,7 @@ func TestGet(t *testing.T) { mockCtrl.Finish() } -func TestGetString(t *testing.T) { +func TestMemory_GetString(t *testing.T) { testCases := []struct { key string get string diff --git a/configuration/json_test.go b/configuration/json_test.go index 6fba398..1ed8f8d 100644 --- a/configuration/json_test.go +++ b/configuration/json_test.go @@ -14,7 +14,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestGet(t *testing.T) { +func TestJson_Get(t *testing.T) { testCases := []struct { configString string key string @@ -56,7 +56,7 @@ func TestGet(t *testing.T) { } } -func TestGetFatal(t *testing.T) { +func TestJson_GetFatal(t *testing.T) { testCases := []struct { configString string key string @@ -90,7 +90,7 @@ func TestGetFatal(t *testing.T) { } } -func TestGetString(t *testing.T) { +func TestJson_GetString(t *testing.T) { testCases := []struct { configString string key string @@ -117,7 +117,7 @@ func TestGetString(t *testing.T) { } } -func TestGetError(t *testing.T) { +func TestJson_GetError(t *testing.T) { testCases := []struct { configString string key string @@ -147,7 +147,7 @@ func TestGetError(t *testing.T) { } } -func TestGetStringFatal(t *testing.T) { +func TestJson_GetStringFatal(t *testing.T) { testCases := []struct { configString string key string @@ -181,7 +181,7 @@ func TestGetStringFatal(t *testing.T) { } } -func TestGetInt(t *testing.T) { +func TestJson_GetInt(t *testing.T) { testCases := []struct { configString string key string @@ -212,7 +212,7 @@ func TestGetInt(t *testing.T) { } } -func TestGetIntFatal(t *testing.T) { +func TestJson_GetIntFatal(t *testing.T) { testCases := []struct { configString string key string @@ -248,7 +248,7 @@ func TestGetIntFatal(t *testing.T) { } } -func TestGetFloat(t *testing.T) { +func TestJson_GetFloat(t *testing.T) { testCases := []struct { configString string key string @@ -280,7 +280,7 @@ func TestGetFloat(t *testing.T) { } } -func TestGetKeys(t *testing.T) { +func TestJson_GetKeys(t *testing.T) { testCases := []struct { configString string expected []string @@ -307,7 +307,7 @@ func TestGetKeys(t *testing.T) { } } -func TestGetConfig(t *testing.T) { +func TestJson_GetConfig(t *testing.T) { testCases := []struct { configString string key string @@ -341,7 +341,7 @@ func TestGetConfig(t *testing.T) { } } -func TestGetStruct(t *testing.T) { +func TestJson_GetStruct(t *testing.T) { testCases := []struct { configString string key string @@ -369,7 +369,7 @@ func TestGetStruct(t *testing.T) { } } -func TestGetStringArray(t *testing.T) { +func TestJson_GetStringArray(t *testing.T) { testCases := []struct { configString string key string @@ -394,7 +394,7 @@ func TestGetStringArray(t *testing.T) { } } -func TestGetBool(t *testing.T) { +func TestJson_GetBool(t *testing.T) { testCases := []struct { configString string key string diff --git a/configuration/sql_test.go b/configuration/sql_test.go index 3c1b641..676a2fb 100644 --- a/configuration/sql_test.go +++ b/configuration/sql_test.go @@ -25,7 +25,7 @@ func TestNewSql(t *testing.T) { assert.Equal(t, mockConfiguration, sqlConfig.Configuration) } -func TestGetMySqlDataBaseSourceName(t *testing.T) { +func TestSql_GetMySqlDataBaseSourceName(t *testing.T) { // arrange mockCtrl := gomock.NewController(t) mockConfiguration := mocks.NewMockConfiguration(mockCtrl) @@ -44,7 +44,7 @@ func TestGetMySqlDataBaseSourceName(t *testing.T) { assert.Equal(t, "DBUSERNAME:DBPASSWORD@DBPROTOCOL(DBADDRESS)/DBNAME?parseTime=true", dbString) } -func TestGetPostgresDataBaseSourceName(t *testing.T) { +func TestSql_GetPostgresDataBaseSourceName(t *testing.T) { // arrange mockCtrl := gomock.NewController(t) mockConfiguration := mocks.NewMockConfiguration(mockCtrl) 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 similarity index 62% rename from file/Storage.go rename to file/storage.go index 61e4e88..26f1372 100644 --- a/file/Storage.go +++ b/file/storage.go @@ -1,5 +1,7 @@ package file +//go:generate mockgen -destination=../mocks/mock_storage.go -package=mocks github.com/reaction-eng/restlib/file Storage + import "io" type Storage interface { 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_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..9f32ec8 --- /dev/null +++ b/mocks/mock_storage.go @@ -0,0 +1,64 @@ +// 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" + 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 +} + +// 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) +} + +// 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/utils/Base64File.go b/utils/Base64File.go index 5491969..0b2610b 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. @@ -39,7 +40,6 @@ func NewBase64FileFromForm(file multipart.File, fileInfo *multipart.FileHeader) } return &b64File, error - } /** From 42462b674951f7f38908785a4396f27863b0f13d Mon Sep 17 00:00:00 2001 From: mcgurn Date: Mon, 25 Nov 2019 20:10:24 -0700 Subject: [PATCH 03/23] added some tests for google. The rest is hard to unit test with limited mocking from google libs --- file/directory.go | 18 ++ file/document.go | 15 ++ file/item.go | 39 ++++ file/storage.go | 11 +- google/DocumentHierarchy.go | 330 ------------------------------ google/Sheets.go | 18 +- google/gDirectory.go | 166 +++++++++++++++ google/gDirectory_test.go | 341 +++++++++++++++++++++++++++++++ google/gDocument.go | 55 +++++ google/gDocument_test.go | 181 ++++++++++++++++ google/{Drive.go => gDrive.go} | 58 +++--- google/gEvent.go | 38 ++++ google/gFile.go | 33 +++ google/gForm.go | 64 ++++++ mocks/mock_storage.go | 72 ++++--- notification/NotifierCalendar.go | 9 +- passwords/BasicHelper.go | 2 +- passwords/ResetRepoSql.go | 7 +- roles/PermissionTableJson.go | 2 +- roles/RepoSql.go | 5 +- routing/Router.go | 2 +- static/RepoCache.go | 5 +- users/RepoMemory.go | 4 +- users/RepoSql.go | 5 +- utils/Base64File.go | 2 +- utils/json.go | 4 +- 26 files changed, 1063 insertions(+), 423 deletions(-) create mode 100644 file/directory.go create mode 100644 file/document.go create mode 100644 file/item.go delete mode 100644 google/DocumentHierarchy.go create mode 100644 google/gDirectory.go create mode 100644 google/gDirectory_test.go create mode 100644 google/gDocument.go create mode 100644 google/gDocument_test.go rename google/{Drive.go => gDrive.go} (93%) create mode 100644 google/gEvent.go create mode 100644 google/gFile.go create mode 100644 google/gForm.go diff --git a/file/directory.go b/file/directory.go new file mode 100644 index 0000000..9413a7f --- /dev/null +++ b/file/directory.go @@ -0,0 +1,18 @@ +package file + +//go:generate mockgen -destination=../mocks/mock_directory.go -package=mocks github.com/reaction-eng/restlib/file Directory + +type Directory interface { + Item + + GetType() string + + GetItems() []Item + + GetParentId() string + + ForEach(doOnFolder bool, do ItemFunc) +} + +//Define a function +type ItemFunc func(item Item) diff --git a/file/document.go b/file/document.go new file mode 100644 index 0000000..02eefbd --- /dev/null +++ b/file/document.go @@ -0,0 +1,15 @@ +package file + +//go:generate mockgen -destination=../mocks/mock_document.go -package=mocks github.com/reaction-eng/restlib/file Document + +type Document interface { + Item + + Type() string + + Preview() string + + ThumbnailUrl() string + + ParentId() string +} diff --git a/file/item.go b/file/item.go new file mode 100644 index 0000000..a494b97 --- /dev/null +++ b/file/item.go @@ -0,0 +1,39 @@ +package file + +//go:generate mockgen -destination=../mocks/mock_storage.go -package=mocks github.com/reaction-eng/restlib/file Item + +import ( + "time" +) + +/** + * An interface type to hold a document or directory + */ +type Item interface { + GetId() string + GetName() string + GetDate() *time.Time +} + +// 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/file/storage.go b/file/storage.go index 26f1372..772f54c 100644 --- a/file/storage.go +++ b/file/storage.go @@ -1,10 +1,19 @@ package file -//go:generate mockgen -destination=../mocks/mock_storage.go -package=mocks github.com/reaction-eng/restlib/file Storage +//go:generate mockgen -destination=../mocks/mock_item.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) + + BuildFileHierarchy(dirId string, buildPreview bool, includeFilter func(fileType string) bool) Directory + BuildFormHierarchy(dirId string) Directory + GetFilePreview(id string) string + GetFileThumbnailUrl(id string) + GetFileHtml(id string) string + GetMostRecentFileInDir(dirId string) (io.ReadCloser, error) + GetFileAsInterface(id string, inter interface{}) + GetFirstFileMatching(dirId string, name string) (io.ReadCloser, error) } 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 29e398e..744f82f 100644 --- a/google/Sheets.go +++ b/google/Sheets.go @@ -5,14 +5,15 @@ package google import ( "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/gDirectory.go b/google/gDirectory.go new file mode 100644 index 0000000..a2af9e7 --- /dev/null +++ b/google/gDirectory.go @@ -0,0 +1,166 @@ +// 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" + "fmt" + + "github.com/reaction-eng/restlib/file" +) + +type gDirectory struct { + gFile + + //Keep the type for directory anyways + Type string `json:"type"` + + //Hold a list of Items + Items []file.Item `json:"Items"` + + //Keep the parent Id, unless this is root and then it is null + ParentId string `json:"parentid"` +} + +func (dir *gDirectory) GetType() string { + return dir.Type +} + +func (dir *gDirectory) GetItems() []file.Item { + return dir.Items +} + +func (dir *gDirectory) GetParentId() string { + return dir.ParentId +} + +func (dir *gDirectory) ForEach(doOnFolder bool, do file.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.(*gDirectory); isDir { + asDir.ForEach(doOnFolder, do) + } else { + //Just apply the function + do(item) + } + } +} + +/** +Custom marashaler +*/ +func (dir gDirectory) MarshalJSON() ([]byte, error) { + //Store a ddir copy + type fakeItem gDirectory + + //define the item type + type ItemWithType struct { + fakeItem + InternalItemType string + } + + return json.Marshal(ItemWithType{ + (fakeItem)(dir), + "gDirectory", + }) +} + +func (dir *gDirectory) 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 gDirectory + + //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([]file.Item, len(rawItemsList)) + + for index, rawMessage := range rawItemsList { + var m map[string]interface{} + + 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 "gDocument": + var tmp gDocument + err := json.Unmarshal(*rawMessage, &tmp) + if err != nil { + return err + } + dir.Items[index] = &tmp + break + case "gDirectory": + var tmp gDirectory + err := json.Unmarshal(*rawMessage, &tmp) + if err != nil { + return err + } + dir.Items[index] = &tmp + break + case "gForm": + var tmp gForm + err := json.Unmarshal(*rawMessage, &tmp) + if err != nil { + return err + } + dir.Items[index] = &tmp + break + case "gEvent": + var tmp gEvent + err := json.Unmarshal(*rawMessage, &tmp) + if err != nil { + return err + } + dir.Items[index] = &tmp + break + default: + var tmp gFile + err := json.Unmarshal(*rawMessage, &tmp) + if err != nil { + return err + } + dir.Items[index] = &tmp + break + } + } + + return nil +} diff --git a/google/gDirectory_test.go b/google/gDirectory_test.go new file mode 100644 index 0000000..287e2c4 --- /dev/null +++ b/google/gDirectory_test.go @@ -0,0 +1,341 @@ +// 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" + "sort" + "testing" + "time" + + "github.com/reaction-eng/restlib/file" + + "github.com/stretchr/testify/assert" +) + +func TestGDirectory_GetId(t *testing.T) { + // arrange + referenceTime := time.Unix(1574622126, 0) + + gDirectory := gDirectory{ + gFile: gFile{ + Id: "12345abc", + Name: "testName", + HideListing: false, + Date: &referenceTime, + }, + } + + // act + testId := gDirectory.GetId() + + //assert + assert.Equal(t, "12345abc", testId) +} + +func TestGDirectory_GetName(t *testing.T) { + // arrange + referenceTime := time.Unix(1574622126, 0) + + gDirectory := gDirectory{ + gFile: gFile{ + Id: "12345abc", + Name: "testName", + HideListing: false, + Date: &referenceTime, + }, + } + + // act + testName := gDirectory.GetName() + + //assert + assert.Equal(t, "testName", testName) +} + +func TestGDirectory_GetDate(t *testing.T) { + // arrange + referenceTime := time.Unix(1574622126, 0) + + gDirectory := gDirectory{ + gFile: gFile{ + Id: "12345abc", + Name: "testName", + HideListing: false, + Date: &referenceTime, + }, + } + + // act + testDate := gDirectory.GetDate() + + //assert + assert.Equal(t, &referenceTime, testDate) +} + +func TestGDirectory_GetType(t *testing.T) { + // arrange + referenceTime := time.Unix(1574622126, 0) + + gDirectory := gDirectory{ + gFile: gFile{ + Id: "12345abc", + Name: "testName", + HideListing: false, + Date: &referenceTime, + }, + Type: "testFileType", + } + + // act + testFileType := gDirectory.GetType() + + //assert + assert.Equal(t, "testFileType", testFileType) +} + +func TestGDirectory_GetItems(t *testing.T) { + // arrange + referenceTime := time.Unix(1574622126, 0) + + expectedItems := []file.Item{ + gFile{ + Id: "subItemId1", + Name: "subItemName1", + HideListing: false, + Date: &referenceTime, + }, + gFile{ + Id: "subItemName2", + Name: "testName", + HideListing: false, + Date: &referenceTime, + }, + } + + gDirectory := gDirectory{ + gFile: gFile{ + Id: "12345abc", + Name: "testName", + HideListing: false, + Date: &referenceTime, + }, + Type: "testFileType", + Items: expectedItems, + } + + // act + testItems := gDirectory.GetItems() + + //assert + assert.Equal(t, expectedItems, testItems) +} + +func TestGDirectory_GetParentId(t *testing.T) { + // arrange + referenceTime := time.Unix(1574622126, 0) + + gDirectory := gDirectory{ + gFile: gFile{ + Id: "12345abc", + Name: "testName", + HideListing: false, + Date: &referenceTime, + }, + Type: "testFileType", + ParentId: "parentId123", + } + + // act + testParentId := gDirectory.GetParentId() + + //assert + assert.Equal(t, "parentId123", testParentId) +} + +func TestGDirectory_ForEach(t *testing.T) { + // arrange + referenceTime := time.Unix(1574622126, 0) + + items := []file.Item{ + &gFile{ + Id: "subItemId1", + Name: "subItemName1", + HideListing: false, + Date: &referenceTime, + }, + &gFile{ + Id: "subItemName2", + Name: "testName", + HideListing: false, + Date: &referenceTime, + }, + &gDirectory{ + gFile: gFile{ + Id: "subDirId1", + }, + Type: "", + ParentId: "", + Items: []file.Item{ + &gFile{ + Id: "subSubItemId1", + Name: "subSubItemName1", + HideListing: false, + Date: &referenceTime, + }, + }, + }, + } + + gDirectory := gDirectory{ + gFile: gFile{ + Id: "12345abc", + Name: "testName", + HideListing: false, + Date: &referenceTime, + }, + Type: "testFileType", + Items: items, + } + + testCases := []struct { + includeDir bool + expected []string + }{ + { + includeDir: false, + expected: []string{"subItemId1", "subItemName2", "subSubItemId1"}, + }, + { + includeDir: true, + expected: []string{"12345abc", "subItemId1", "subItemName2", "subSubItemId1", "subDirId1"}, + }, + } + for _, testCase := range testCases { + // act + calledCommands := make([]string, 0) + gDirectory.ForEach(testCase.includeDir, func(item file.Item) { + calledCommands = append(calledCommands, item.GetId()) + }) + + //assert + sort.Strings(calledCommands) + sort.Strings(testCase.expected) + assert.Equal(t, testCase.expected, calledCommands) + } +} + +func TestGDirectory_MarshalJSON(t *testing.T) { + // arrange + referenceTime := time.Unix(1574622126, 0) + + items := []file.Item{ + &gDocument{ + gFile: gFile{ + Id: "subItemId1", + Name: "subItemName1", + HideListing: false, + Date: &referenceTime, + }, + Preview: "Preview123", + }, + &gFile{ + Id: "subItemName2", + Name: "testName", + HideListing: false, + Date: &referenceTime, + }, + &gDirectory{ + gFile: gFile{ + Id: "subDirId1", + }, + Type: "", + ParentId: "", + Items: []file.Item{ + &gFile{ + Id: "subSubItemId1", + Name: "subSubItemName1", + HideListing: false, + Date: &referenceTime, + }, + }, + }, + } + + gDirectory := gDirectory{ + gFile: gFile{ + Id: "12345abc", + Name: "testName", + HideListing: false, + Date: &referenceTime, + }, + Type: "testFileType", + Items: items, + } + + // act + jsonGDirectory, _ := json.Marshal(gDirectory) + + //assert + assert.Equal(t, `{"Id":"12345abc","name":"testName","hideListing":false,"date":"2019-11-24T12:02:06-07:00","type":"testFileType","Items":[{"Id":"subItemId1","name":"subItemName1","hideListing":false,"date":"2019-11-24T12:02:06-07:00","type":"","preview":"Preview123","thumbnail":"","parentid":"","InternalItemType":"gDocument"},{"Id":"subItemName2","name":"testName","hideListing":false,"date":"2019-11-24T12:02:06-07:00"},{"Id":"subDirId1","name":"","hideListing":false,"date":null,"type":"","Items":[{"Id":"subSubItemId1","name":"subSubItemName1","hideListing":false,"date":"2019-11-24T12:02:06-07:00"}],"parentid":"","InternalItemType":"gDirectory"}],"parentid":"","InternalItemType":"gDirectory"}`, string(jsonGDirectory)) +} + +func TestGDirectory_UnmarshalJSON(t *testing.T) { + // arrange + referenceTime := time.Unix(1574622126, 0) + + expectedItems := []file.Item{ + &gDocument{ + gFile: gFile{ + Id: "subItemId1", + Name: "subItemName1", + HideListing: false, + Date: &referenceTime, + }, + Preview: "Preview123", + }, + &gFile{ + Id: "subItemName2", + Name: "testName", + HideListing: false, + Date: &referenceTime, + }, + &gDirectory{ + gFile: gFile{ + Id: "subDirId1", + }, + Type: "", + ParentId: "", + Items: []file.Item{ + &gFile{ + Id: "subSubItemId1", + Name: "subSubItemName1", + HideListing: false, + Date: &referenceTime, + }, + }, + }, + } + + expectedGDirectory := gDirectory{ + gFile: gFile{ + Id: "12345abc", + Name: "testName", + HideListing: false, + Date: &referenceTime, + }, + Type: "testFileType", + Items: expectedItems, + } + + jsonString := `{"Id":"12345abc","name":"testName","hideListing":false,"date":"2019-11-24T12:02:06-07:00","type":"testFileType","Items":[{"Id":"subItemId1","name":"subItemName1","hideListing":false,"date":"2019-11-24T12:02:06-07:00","type":"","preview":"Preview123","thumbnail":"","parentid":"","InternalItemType":"gDocument"},{"Id":"subItemName2","name":"testName","hideListing":false,"date":"2019-11-24T12:02:06-07:00"},{"Id":"subDirId1","name":"","hideListing":false,"date":null,"type":"","Items":[{"Id":"subSubItemId1","name":"subSubItemName1","hideListing":false,"date":"2019-11-24T12:02:06-07:00"}],"parentid":"","InternalItemType":"gDirectory"}],"parentid":"","InternalItemType":"gDirectory"}` + + // act + var testGDirectory gDirectory + err := json.Unmarshal([]byte(jsonString), &testGDirectory) + + //assert + assert.Nil(t, err) + assert.EqualValues(t, expectedGDirectory, testGDirectory) +} diff --git a/google/gDocument.go b/google/gDocument.go new file mode 100644 index 0000000..84022f0 --- /dev/null +++ b/google/gDocument.go @@ -0,0 +1,55 @@ +// 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" +) + +type gDocument struct { + gFile + + 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 (doc *gDocument) GetType() string { + return doc.Type +} + +func (doc *gDocument) GetParentId() string { + return doc.ParentId +} + +func (doc *gDocument) GetPreview() string { + return doc.Preview +} + +func (doc *gDocument) GetThumbnailUrl() string { + return doc.ThumbnailUrl +} + +func (doc gDocument) MarshalJSON() ([]byte, error) { + //Store a dir copy + type fakeItem gDocument + + //define the item type + type ItemWithType struct { + fakeItem + InternalItemType string + } + + return json.Marshal(ItemWithType{ + (fakeItem)(doc), + "gDocument", + }) +} diff --git a/google/gDocument_test.go b/google/gDocument_test.go new file mode 100644 index 0000000..93b7def --- /dev/null +++ b/google/gDocument_test.go @@ -0,0 +1,181 @@ +// 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" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestGDocument_GetId(t *testing.T) { + // arrange + referenceTime := time.Unix(1574622126, 0) + + gDocument := gDocument{ + gFile: gFile{ + Id: "12345abc", + Name: "testName", + HideListing: false, + Date: &referenceTime, + }, + } + + // act + testId := gDocument.GetId() + + //assert + assert.Equal(t, "12345abc", testId) +} + +func TestGDocument_GetName(t *testing.T) { + // arrange + referenceTime := time.Unix(1574622126, 0) + + gDocument := gDocument{ + gFile: gFile{ + Id: "12345abc", + Name: "testName", + HideListing: false, + Date: &referenceTime, + }, + } + + // act + testName := gDocument.GetName() + + //assert + assert.Equal(t, "testName", testName) +} + +func TestGDocument_GetDate(t *testing.T) { + // arrange + referenceTime := time.Unix(1574622126, 0) + + gDocument := gDocument{ + gFile: gFile{ + Id: "12345abc", + Name: "testName", + HideListing: false, + Date: &referenceTime, + }, + } + + // act + testDate := gDocument.GetDate() + + //assert + assert.Equal(t, &referenceTime, testDate) +} + +func TestGDocument_GetType(t *testing.T) { + // arrange + referenceTime := time.Unix(1574622126, 0) + + gDocument := gDocument{ + gFile: gFile{ + Id: "12345abc", + Name: "testName", + HideListing: false, + Date: &referenceTime, + }, + Type: "testFileType", + } + + // act + testFileType := gDocument.GetType() + + //assert + assert.Equal(t, "testFileType", testFileType) +} + +func TestGDocument_GetPreview(t *testing.T) { + // arrange + referenceTime := time.Unix(1574622126, 0) + + gDocument := gDocument{ + gFile: gFile{ + Id: "12345abc", + Name: "testName", + HideListing: false, + Date: &referenceTime, + }, + Preview: "testFilePreview", + } + + // act + testPreview := gDocument.GetPreview() + + //assert + assert.Equal(t, "testFilePreview", testPreview) +} + +func TestGDocument_GetThumbnailUrl(t *testing.T) { + // arrange + referenceTime := time.Unix(1574622126, 0) + + gDocument := gDocument{ + gFile: gFile{ + Id: "12345abc", + Name: "testName", + HideListing: false, + Date: &referenceTime, + }, + ThumbnailUrl: "thumbnailUrl", + } + + // act + testThumbnailUrl := gDocument.GetThumbnailUrl() + + //assert + assert.Equal(t, "thumbnailUrl", testThumbnailUrl) +} + +func TestGDocument_GetParentId(t *testing.T) { + // arrange + referenceTime := time.Unix(1574622126, 0) + + gDocument := gDocument{ + gFile: gFile{ + Id: "12345abc", + Name: "testName", + HideListing: false, + Date: &referenceTime, + }, + Type: "testFileType", + ParentId: "parentId123", + } + + // act + testParentId := gDocument.GetParentId() + + //assert + assert.Equal(t, "parentId123", testParentId) +} + +func TestGDocument_MarshalJSON(t *testing.T) { + // arrange + referenceTime := time.Unix(1574622126, 0) + + gDocument := gDocument{ + gFile: gFile{ + Id: "12345abc", + Name: "testName", + HideListing: false, + Date: &referenceTime, + }, + Type: "testFileType", + Preview: "testFilePreview", + ThumbnailUrl: "utl//url", + ParentId: "IHaveNoFather", + } + + // act + jsonGDocument, _ := json.Marshal(gDocument) + + //assert + assert.Equal(t, `{"Id":"12345abc","name":"testName","hideListing":false,"date":"2019-11-24T12:02:06-07:00","type":"testFileType","preview":"testFilePreview","thumbnail":"utl//url","parentid":"IHaveNoFather","InternalItemType":"gDocument"}`, string(jsonGDocument)) +} diff --git a/google/Drive.go b/google/gDrive.go similarity index 93% rename from google/Drive.go rename to google/gDrive.go index ee4cbfa..e229c71 100644 --- a/google/Drive.go +++ b/google/gDrive.go @@ -7,12 +7,6 @@ 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" @@ -20,6 +14,14 @@ import ( "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 { @@ -35,19 +37,16 @@ type Drive struct { } //Get a new interface -func NewDrive(configFiles ...string) *Drive { - //Create a new config - config, err := configuration.NewConfiguration(configFiles...) - +func NewDrive(configuration configuration.Configuration) *Drive { //Create a new gInter := &Drive{ - timeZone: config.GetStringFatal("default_time_zone"), + timeZone: configuration.GetStringFatal("default_time_zone"), } //Open the client jwtConfig := &jwt.Config{ - Email: config.GetStringFatal("google_auth_email"), - PrivateKey: []byte(config.GetStringFatal("google_auth_key")), + Email: configuration.GetStringFatal("google_auth_email"), + PrivateKey: []byte(configuration.GetStringFatal("google_auth_key")), Scopes: []string{ drive.DriveMetadataReadonlyScope, drive.DriveReadonlyScope, @@ -68,7 +67,7 @@ func NewDrive(configFiles ...string) *Drive { log.Fatalf("Unable to retrieve Drive client: %v", err) } - gInter.previewLength, _ = config.GetInt("preview_length") + gInter.previewLength, _ = configuration.GetInt("preview_length") return gInter } @@ -134,7 +133,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) BuildFileHierarchy(dirId string, buildPreview bool, includeFilter func(fileType string) bool) *gDirectory { //Get this item folderInfo, err := gog.connection.Files. @@ -151,13 +150,13 @@ func (gog *Drive) BuildFileHierarchy(dirId string, buildPreview bool, includeFil name, date := gog.splitNameAndDate(folderInfo.Name) //Get all of the files in this folder - dir := &Directory{ - File: File{ + dir := &gDirectory{ + gFile: gFile{ Id: folderInfo.Id, Name: name, }, Type: folderInfo.MimeType, - Items: make([]Item, 0), + Items: make([]file.Item, 0), } //If there is a date add it @@ -187,7 +186,7 @@ func (gog *Drive) BuildFileHierarchy(dirId string, buildPreview bool, includeFil //Get the child childFolder := gog.BuildFileHierarchy(item.Id, buildPreview, includeFilter) - //Now set the parent id to this + //Now set the parent Id to this childFolder.ParentId = dir.Id //Just add the child @@ -198,8 +197,8 @@ func (gog *Drive) BuildFileHierarchy(dirId string, buildPreview bool, includeFil name, date := gog.splitNameAndDate(item.Name) //Create a new document - doc := &Document{ - File: File{ + doc := &gDocument{ + gFile: gFile{ Id: item.Id, Name: name, }, @@ -221,7 +220,6 @@ func (gog *Drive) BuildFileHierarchy(dirId string, buildPreview bool, includeFil //Store it dir.Items = append(dir.Items, doc) - } } } @@ -232,7 +230,7 @@ func (gog *Drive) BuildFileHierarchy(dirId string, buildPreview bool, includeFil /** Builds all of the forms and downloads them at the same time */ -func (gog *Drive) BuildFormHierarchy(dirId string) *Directory { +func (gog *Drive) BuildFormHierarchy(dirId string) *gDirectory { //Get this item folderInfo, err := gog.connection.Files. @@ -246,13 +244,13 @@ func (gog *Drive) BuildFormHierarchy(dirId string) *Directory { } //Get all of the files in this folder - dir := &Directory{ - File: File{ + dir := &gDirectory{ + gFile: gFile{ Id: folderInfo.Id, Name: folderInfo.Name, }, Type: folderInfo.MimeType, - Items: make([]Item, 0), + Items: make([]file.Item, 0), } //Now get all of the files @@ -277,7 +275,7 @@ func (gog *Drive) BuildFormHierarchy(dirId string) *Directory { //Get the child childFolder := gog.BuildFormHierarchy(item.Id) - //Now set the parent id to this + //Now set the parent Id to this childFolder.ParentId = dir.Id //Only add the form is there are any children @@ -298,7 +296,7 @@ func (gog *Drive) BuildFormHierarchy(dirId string) *Directory { //Remove the extention name := strings.TrimSuffix(item.Name, filepath.Ext(item.Name)) - //Add the forms id + //Add the forms Id form.Id = item.Id form.Name = name form.ParentId = dir.Id @@ -361,7 +359,7 @@ func (gog *Drive) GetFilePreview(id string) string { /** * Method to get the information hierarchy */ -func (gog *Drive) downloadForm(id string) (*Form, error) { +func (gog *Drive) downloadForm(id string) (*gForm, error) { //Get the plain text version of the file resp, err := gog.connection.Files.Get(id).Download() @@ -375,7 +373,7 @@ func (gog *Drive) downloadForm(id string) (*Form, error) { dec := json.NewDecoder(resp.Body) //Createa a new forms - form := &Form{} + form := &gForm{} //Now decode the stream into the forms err = dec.Decode(form) diff --git a/google/gEvent.go b/google/gEvent.go new file mode 100644 index 0000000..1339b1c --- /dev/null +++ b/google/gEvent.go @@ -0,0 +1,38 @@ +// 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" +) + +//Hold the struct need to create a tree +type gEvent struct { + gFile + + //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 gEvent) MarshalJSON() ([]byte, error) { + //Store a ddir copy + type fakeItem gEvent + + //define the item type + type ItemWithType struct { + fakeItem + InternalItemType string + } + + return json.Marshal(ItemWithType{ + (fakeItem)(dir), + "gEvent", + }) +} diff --git a/google/gFile.go b/google/gFile.go new file mode 100644 index 0000000..edcf187 --- /dev/null +++ b/google/gFile.go @@ -0,0 +1,33 @@ +// 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 ( + "time" +) + +type gFile 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 gFile) GetId() string { + return file.Id +} +func (file gFile) GetName() string { + return file.Name +} +func (file gFile) GetDate() *time.Time { + return file.Date +} diff --git a/google/gForm.go b/google/gForm.go new file mode 100644 index 0000000..b839020 --- /dev/null +++ b/google/gForm.go @@ -0,0 +1,64 @@ +// 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" +) + +//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 gForm struct { + gFile + //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 gForm) MarshalJSON() ([]byte, error) { + //Store a ddir copy + type fakeItem gForm + + //define the item type + type ItemWithType struct { + fakeItem + InternalItemType string + } + + return json.Marshal(ItemWithType{ + (fakeItem)(dir), + "gForm", + }) +} diff --git a/mocks/mock_storage.go b/mocks/mock_storage.go index 9f32ec8..bb6420d 100644 --- a/mocks/mock_storage.go +++ b/mocks/mock_storage.go @@ -1,64 +1,76 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/reaction-eng/restlib/file (interfaces: Storage) +// Source: github.com/reaction-eng/restlib/file (interfaces: Item) // Package mocks is a generated GoMock package. package mocks import ( gomock "github.com/golang/mock/gomock" - io "io" reflect "reflect" + time "time" ) -// MockStorage is a mock of Storage interface -type MockStorage struct { +// MockItem is a mock of Item interface +type MockItem struct { ctrl *gomock.Controller - recorder *MockStorageMockRecorder + recorder *MockItemMockRecorder } -// MockStorageMockRecorder is the mock recorder for MockStorage -type MockStorageMockRecorder struct { - mock *MockStorage +// MockItemMockRecorder is the mock recorder for MockItem +type MockItemMockRecorder struct { + mock *MockItem } -// NewMockStorage creates a new mock instance -func NewMockStorage(ctrl *gomock.Controller) *MockStorage { - mock := &MockStorage{ctrl: ctrl} - mock.recorder = &MockStorageMockRecorder{mock} +// NewMockItem creates a new mock instance +func NewMockItem(ctrl *gomock.Controller) *MockItem { + mock := &MockItem{ctrl: ctrl} + mock.recorder = &MockItemMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use -func (m *MockStorage) EXPECT() *MockStorageMockRecorder { +func (m *MockItem) EXPECT() *MockItemMockRecorder { return m.recorder } -// GetArbitraryFile mocks base method -func (m *MockStorage) GetArbitraryFile(arg0 string) (io.ReadCloser, error) { +// GetDate mocks base method +func (m *MockItem) GetDate() *time.Time { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetArbitraryFile", arg0) - ret0, _ := ret[0].(io.ReadCloser) - ret1, _ := ret[1].(error) - return ret0, ret1 + ret := m.ctrl.Call(m, "GetDate") + ret0, _ := ret[0].(*time.Time) + return ret0 } -// GetArbitraryFile indicates an expected call of GetArbitraryFile -func (mr *MockStorageMockRecorder) GetArbitraryFile(arg0 interface{}) *gomock.Call { +// GetDate indicates an expected call of GetDate +func (mr *MockItemMockRecorder) GetDate() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetArbitraryFile", reflect.TypeOf((*MockStorage)(nil).GetArbitraryFile), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDate", reflect.TypeOf((*MockItem)(nil).GetDate)) } -// PostArbitraryFile mocks base method -func (m *MockStorage) PostArbitraryFile(arg0, arg1 string, arg2 io.Reader, arg3 string) (string, error) { +// GetId mocks base method +func (m *MockItem) GetId() string { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PostArbitraryFile", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "GetId") ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 + return ret0 } -// PostArbitraryFile indicates an expected call of PostArbitraryFile -func (mr *MockStorageMockRecorder) PostArbitraryFile(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +// GetId indicates an expected call of GetId +func (mr *MockItemMockRecorder) GetId() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PostArbitraryFile", reflect.TypeOf((*MockStorage)(nil).PostArbitraryFile), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetId", reflect.TypeOf((*MockItem)(nil).GetId)) +} + +// GetName mocks base method +func (m *MockItem) GetName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetName") + ret0, _ := ret[0].(string) + return ret0 +} + +// GetName indicates an expected call of GetName +func (mr *MockItemMockRecorder) GetName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetName", reflect.TypeOf((*MockItem)(nil).GetName)) } 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/passwords/BasicHelper.go b/passwords/BasicHelper.go index 3e05073..5cda88b 100644 --- a/passwords/BasicHelper.go +++ b/passwords/BasicHelper.go @@ -16,7 +16,7 @@ import ( ) /** -File of static support functions for passwords creating, editing, hashing, etc. +gFile of static support functions for passwords creating, editing, hashing, etc. */ type BasicHelper struct { diff --git a/passwords/ResetRepoSql.go b/passwords/ResetRepoSql.go index a3fa5de..2f79376 100644 --- a/passwords/ResetRepoSql.go +++ b/passwords/ResetRepoSql.go @@ -6,10 +6,11 @@ package passwords import ( "database/sql" "errors" - "github.com/reaction-eng/restlib/configuration" - "github.com/reaction-eng/restlib/email" "log" "time" + + "github.com/reaction-eng/restlib/configuration" + "github.com/reaction-eng/restlib/email" ) /** @@ -328,7 +329,7 @@ func (repo *ResetRepoSql) CleanUp() { //func RepoDestroyCalc(id int) error { // for i, t := range usersList { -// if t.Id == id { +// if t.id == id { // usersList = append(usersList[:i], usersList[i+1:]...) // return nil // } diff --git a/roles/PermissionTableJson.go b/roles/PermissionTableJson.go index 595701a..8389fb3 100644 --- a/roles/PermissionTableJson.go +++ b/roles/PermissionTableJson.go @@ -78,7 +78,7 @@ func (repo *PermissionTableJson) LookUpRoleId(roleLookUp string) (int, error) { //func RepoDestroyCalc(id int) error { // for i, t := range usersList { -// if t.Id == id { +// if t.id == id { // usersList = append(usersList[:i], usersList[i+1:]...) // return nil // } diff --git a/roles/RepoSql.go b/roles/RepoSql.go index 98de884..915677b 100644 --- a/roles/RepoSql.go +++ b/roles/RepoSql.go @@ -5,8 +5,9 @@ package roles import ( "database/sql" - "github.com/reaction-eng/restlib/users" "log" + + "github.com/reaction-eng/restlib/users" ) /** @@ -270,7 +271,7 @@ func sameRoles(a, b []int) bool { //func RepoDestroyCalc(id int) error { // for i, t := range usersList { -// if t.Id == id { +// if t.id == id { // usersList = append(usersList[:i], usersList[i+1:]...) // return nil // } diff --git a/routing/Router.go b/routing/Router.go index 5626dec..b55c806 100644 --- a/routing/Router.go +++ b/routing/Router.go @@ -33,7 +33,7 @@ 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 /** diff --git a/static/RepoCache.go b/static/RepoCache.go index c9ab945..9526269 100644 --- a/static/RepoCache.go +++ b/static/RepoCache.go @@ -6,7 +6,6 @@ package static import ( "github.com/reaction-eng/restlib/cache" "github.com/reaction-eng/restlib/configuration" - "github.com/reaction-eng/restlib/google" ) /** @@ -17,7 +16,7 @@ type RepoCache struct { cas cache.Cache //We also need googl - drive *google.Drive + drive *google.gDrive //Store the public and private privateConfig *configuration.Configuration @@ -25,7 +24,7 @@ type RepoCache struct { } //Provide a method to make a new AnimalRepoSql -func NewRepoCache(drive *google.Drive, cas cache.Cache, privateConfigFile string, publicConfigFile string) *RepoCache { +func NewRepoCache(drive *google.gDrive, cas cache.Cache, privateConfigFile string, publicConfigFile string) *RepoCache { //Create a new config privateConfig, _ := configuration.NewConfiguration(privateConfigFile) diff --git a/users/RepoMemory.go b/users/RepoMemory.go index cfb864e..b7a6b4a 100644 --- a/users/RepoMemory.go +++ b/users/RepoMemory.go @@ -94,7 +94,7 @@ func (repo *RepoMemory) 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()) + //_, err := repo.updateUserStatement.Exec(user.Email(), user.Password(), user.id()) // ////Check for error //if err != nil { @@ -136,7 +136,7 @@ Activate User //func RepoDestroyCalc(id int) error { // for i, t := range usersList { -// if t.Id == id { +// if t.id == id { // usersList = append(usersList[:i], usersList[i+1:]...) // return nil // } diff --git a/users/RepoSql.go b/users/RepoSql.go index 22e3851..5d8db5b 100644 --- a/users/RepoSql.go +++ b/users/RepoSql.go @@ -6,10 +6,11 @@ package users import ( "database/sql" "errors" - "github.com/reaction-eng/restlib/utils" "log" "strings" "time" + + "github.com/reaction-eng/restlib/utils" ) /** @@ -378,7 +379,7 @@ func (repo *RepoSql) NewEmptyUser() User { //func RepoDestroyCalc(id int) error { // for i, t := range usersList { -// if t.Id == id { +// if t.id == id { // usersList = append(usersList[:i], usersList[i+1:]...) // return nil // } diff --git a/utils/Base64File.go b/utils/Base64File.go index 0b2610b..1092d53 100644 --- a/utils/Base64File.go +++ b/utils/Base64File.go @@ -36,7 +36,7 @@ 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/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 From 56e97afe7e9413c431bf1f276c5d2c520a552e15 Mon Sep 17 00:00:00 2001 From: mcgurn Date: Fri, 29 Nov 2019 12:56:33 -0700 Subject: [PATCH 04/23] added tests for middleware --- middleware/{CORSHandler.go => corsHandler.go} | 3 +- middleware/corsHandler_test.go | 40 ++ middleware/{HttpsOnly.go => httpsOnly.go} | 9 +- middleware/httpsOnly_test.go | 58 +++ middleware/{JwtHandler.go => jwtHandler.go} | 33 +- middleware/jwtHandler_test.go | 422 ++++++++++++++++++ mocks/mock_directory.go | 131 ++++++ mocks/mock_document.go | 132 ++++++ mocks/mock_helper.go | 119 +++++ mocks/mock_item.go | 175 ++++++++ mocks/mock_roles_repo.go | 78 ++++ mocks/mock_router.go | 49 ++ mocks/mock_users_repo.go | 164 +++++++ mocks/user.go | 165 +++++++ passwords/BasicHelper.go | 12 +- passwords/Helper.go | 2 + passwords/ResetRepoSql.go | 39 +- roles/Permissions.go | 3 - roles/Repo.go | 2 + routing/{Router.go => MuxRouter.go} | 10 +- routing/router.go | 9 + users/Facebook.go | 14 +- users/Google.go | 12 +- users/Repo.go | 2 + users/User.go | 2 + users/User_test.go | 9 +- 26 files changed, 1608 insertions(+), 86 deletions(-) rename middleware/{CORSHandler.go => corsHandler.go} (99%) create mode 100644 middleware/corsHandler_test.go rename middleware/{HttpsOnly.go => httpsOnly.go} (85%) create mode 100644 middleware/httpsOnly_test.go rename middleware/{JwtHandler.go => jwtHandler.go} (86%) create mode 100644 middleware/jwtHandler_test.go create mode 100644 mocks/mock_directory.go create mode 100644 mocks/mock_document.go create mode 100644 mocks/mock_helper.go create mode 100644 mocks/mock_item.go create mode 100644 mocks/mock_roles_repo.go create mode 100644 mocks/mock_router.go create mode 100644 mocks/mock_users_repo.go create mode 100644 mocks/user.go rename routing/{Router.go => MuxRouter.go} (91%) create mode 100644 routing/router.go 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 86% rename from middleware/JwtHandler.go rename to middleware/jwtHandler.go index e7d858f..8e05e78 100644 --- a/middleware/JwtHandler.go +++ b/middleware/jwtHandler.go @@ -4,6 +4,10 @@ package middleware import ( + "errors" + "net/http" + "strings" + "github.com/gorilla/mux" "github.com/reaction-eng/restlib/passwords" "github.com/reaction-eng/restlib/roles" @@ -11,14 +15,12 @@ import ( "github.com/reaction-eng/restlib/users" "github.com/reaction-eng/restlib/utils" "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,6 +66,11 @@ 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) @@ -70,7 +78,6 @@ func MakeJwtMiddlewareFunc(router *routing.Router, userRepo users.Repo, permRepo 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,6 +102,7 @@ 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 has permission diff --git a/middleware/jwtHandler_test.go b/middleware/jwtHandler_test.go new file mode 100644 index 0000000..402a510 --- /dev/null +++ b/middleware/jwtHandler_test.go @@ -0,0 +1,422 @@ +// 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 ( + "context" + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/reaction-eng/restlib/roles" + + "github.com/reaction-eng/restlib/routing" + + "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, "", 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, "", 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, "", 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, "", 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, "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, "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, "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) + + mockRoles.EXPECT().GetPermissions(mockUser).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, "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) + + mockRoles.EXPECT().GetPermissions(mockUser).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, "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) + + mockRoles.EXPECT().GetPermissions(mockUser).Times(1).Return(&roles.Permissions{ + Permissions: []string{}, + }, errors.New("permission error")) + }, + http.StatusForbidden, + "{\"message\":\"insufficient_access\",\"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, "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) + + mockRoles.EXPECT().GetPermissions(mockUser).Times(1).Return(&roles.Permissions{ + Permissions: []string{}, + }, nil) + }, + http.StatusOK, + "", + true, + map[string]interface{}{"user": 100}, + }, + { + "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, "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) + + mockRoles.EXPECT().GetPermissions(mockUser).Times(1).Return(&roles.Permissions{ + Permissions: []string{"perm 1", "perm 2", "req_perm", "perm 4"}, + }, nil) + }, + http.StatusOK, + "", + true, + map[string]interface{}{"user": 100}, + }, + } + + 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 { + ctx := context.WithValue(r.Context(), k, v) + r = r.WithContext(ctx) + } + + assert.Equal(t, w, wResponse, testCase.description) + assert.Equal(t, r, rResponse, 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/mocks/mock_directory.go b/mocks/mock_directory.go new file mode 100644 index 0000000..0af1118 --- /dev/null +++ b/mocks/mock_directory.go @@ -0,0 +1,131 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/reaction-eng/restlib/file (interfaces: Directory) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + gomock "github.com/golang/mock/gomock" + file "github.com/reaction-eng/restlib/file" + reflect "reflect" + time "time" +) + +// MockDirectory is a mock of Directory interface +type MockDirectory struct { + ctrl *gomock.Controller + recorder *MockDirectoryMockRecorder +} + +// MockDirectoryMockRecorder is the mock recorder for MockDirectory +type MockDirectoryMockRecorder struct { + mock *MockDirectory +} + +// NewMockDirectory creates a new mock instance +func NewMockDirectory(ctrl *gomock.Controller) *MockDirectory { + mock := &MockDirectory{ctrl: ctrl} + mock.recorder = &MockDirectoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockDirectory) EXPECT() *MockDirectoryMockRecorder { + return m.recorder +} + +// ForEach mocks base method +func (m *MockDirectory) ForEach(arg0 bool, arg1 file.ItemFunc) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "ForEach", arg0, arg1) +} + +// ForEach indicates an expected call of ForEach +func (mr *MockDirectoryMockRecorder) ForEach(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ForEach", reflect.TypeOf((*MockDirectory)(nil).ForEach), arg0, arg1) +} + +// GetDate mocks base method +func (m *MockDirectory) GetDate() *time.Time { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDate") + ret0, _ := ret[0].(*time.Time) + return ret0 +} + +// GetDate indicates an expected call of GetDate +func (mr *MockDirectoryMockRecorder) GetDate() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDate", reflect.TypeOf((*MockDirectory)(nil).GetDate)) +} + +// GetId mocks base method +func (m *MockDirectory) GetId() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetId") + ret0, _ := ret[0].(string) + return ret0 +} + +// GetId indicates an expected call of GetId +func (mr *MockDirectoryMockRecorder) GetId() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetId", reflect.TypeOf((*MockDirectory)(nil).GetId)) +} + +// GetItems mocks base method +func (m *MockDirectory) GetItems() []file.Item { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetItems") + ret0, _ := ret[0].([]file.Item) + return ret0 +} + +// GetItems indicates an expected call of GetItems +func (mr *MockDirectoryMockRecorder) GetItems() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetItems", reflect.TypeOf((*MockDirectory)(nil).GetItems)) +} + +// GetName mocks base method +func (m *MockDirectory) GetName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetName") + ret0, _ := ret[0].(string) + return ret0 +} + +// GetName indicates an expected call of GetName +func (mr *MockDirectoryMockRecorder) GetName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetName", reflect.TypeOf((*MockDirectory)(nil).GetName)) +} + +// GetParentId mocks base method +func (m *MockDirectory) GetParentId() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetParentId") + ret0, _ := ret[0].(string) + return ret0 +} + +// GetParentId indicates an expected call of GetParentId +func (mr *MockDirectoryMockRecorder) GetParentId() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetParentId", reflect.TypeOf((*MockDirectory)(nil).GetParentId)) +} + +// GetType mocks base method +func (m *MockDirectory) GetType() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetType") + ret0, _ := ret[0].(string) + return ret0 +} + +// GetType indicates an expected call of GetType +func (mr *MockDirectoryMockRecorder) GetType() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetType", reflect.TypeOf((*MockDirectory)(nil).GetType)) +} diff --git a/mocks/mock_document.go b/mocks/mock_document.go new file mode 100644 index 0000000..5cc9b52 --- /dev/null +++ b/mocks/mock_document.go @@ -0,0 +1,132 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/reaction-eng/restlib/file (interfaces: Document) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + gomock "github.com/golang/mock/gomock" + reflect "reflect" + time "time" +) + +// MockDocument is a mock of Document interface +type MockDocument struct { + ctrl *gomock.Controller + recorder *MockDocumentMockRecorder +} + +// MockDocumentMockRecorder is the mock recorder for MockDocument +type MockDocumentMockRecorder struct { + mock *MockDocument +} + +// NewMockDocument creates a new mock instance +func NewMockDocument(ctrl *gomock.Controller) *MockDocument { + mock := &MockDocument{ctrl: ctrl} + mock.recorder = &MockDocumentMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockDocument) EXPECT() *MockDocumentMockRecorder { + return m.recorder +} + +// GetDate mocks base method +func (m *MockDocument) GetDate() *time.Time { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDate") + ret0, _ := ret[0].(*time.Time) + return ret0 +} + +// GetDate indicates an expected call of GetDate +func (mr *MockDocumentMockRecorder) GetDate() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDate", reflect.TypeOf((*MockDocument)(nil).GetDate)) +} + +// GetId mocks base method +func (m *MockDocument) GetId() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetId") + ret0, _ := ret[0].(string) + return ret0 +} + +// GetId indicates an expected call of GetId +func (mr *MockDocumentMockRecorder) GetId() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetId", reflect.TypeOf((*MockDocument)(nil).GetId)) +} + +// GetName mocks base method +func (m *MockDocument) GetName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetName") + ret0, _ := ret[0].(string) + return ret0 +} + +// GetName indicates an expected call of GetName +func (mr *MockDocumentMockRecorder) GetName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetName", reflect.TypeOf((*MockDocument)(nil).GetName)) +} + +// ParentId mocks base method +func (m *MockDocument) ParentId() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParentId") + ret0, _ := ret[0].(string) + return ret0 +} + +// ParentId indicates an expected call of ParentId +func (mr *MockDocumentMockRecorder) ParentId() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParentId", reflect.TypeOf((*MockDocument)(nil).ParentId)) +} + +// Preview mocks base method +func (m *MockDocument) Preview() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Preview") + ret0, _ := ret[0].(string) + return ret0 +} + +// Preview indicates an expected call of Preview +func (mr *MockDocumentMockRecorder) Preview() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Preview", reflect.TypeOf((*MockDocument)(nil).Preview)) +} + +// ThumbnailUrl mocks base method +func (m *MockDocument) ThumbnailUrl() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ThumbnailUrl") + ret0, _ := ret[0].(string) + return ret0 +} + +// ThumbnailUrl indicates an expected call of ThumbnailUrl +func (mr *MockDocumentMockRecorder) ThumbnailUrl() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ThumbnailUrl", reflect.TypeOf((*MockDocument)(nil).ThumbnailUrl)) +} + +// Type mocks base method +func (m *MockDocument) Type() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Type") + ret0, _ := ret[0].(string) + return ret0 +} + +// Type indicates an expected call of Type +func (mr *MockDocumentMockRecorder) Type() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Type", reflect.TypeOf((*MockDocument)(nil).Type)) +} diff --git a/mocks/mock_helper.go b/mocks/mock_helper.go new file mode 100644 index 0000000..93f1b81 --- /dev/null +++ b/mocks/mock_helper.go @@ -0,0 +1,119 @@ +// 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 int, arg1 string) string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateJWTToken", arg0, arg1) + ret0, _ := ret[0].(string) + return ret0 +} + +// CreateJWTToken indicates an expected call of CreateJWTToken +func (mr *MockHelperMockRecorder) CreateJWTToken(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateJWTToken", reflect.TypeOf((*MockHelper)(nil).CreateJWTToken), arg0, arg1) +} + +// 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, string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidateToken", arg0) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(string) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// 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_item.go b/mocks/mock_item.go new file mode 100644 index 0000000..5648271 --- /dev/null +++ b/mocks/mock_item.go @@ -0,0 +1,175 @@ +// 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 +} + +// BuildFileHierarchy mocks base method +func (m *MockStorage) BuildFileHierarchy(arg0 string, arg1 bool, arg2 func(string) bool) file.Directory { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BuildFileHierarchy", arg0, arg1, arg2) + ret0, _ := ret[0].(file.Directory) + return ret0 +} + +// BuildFileHierarchy indicates an expected call of BuildFileHierarchy +func (mr *MockStorageMockRecorder) BuildFileHierarchy(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildFileHierarchy", reflect.TypeOf((*MockStorage)(nil).BuildFileHierarchy), arg0, arg1, arg2) +} + +// BuildFormHierarchy mocks base method +func (m *MockStorage) BuildFormHierarchy(arg0 string) file.Directory { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BuildFormHierarchy", arg0) + ret0, _ := ret[0].(file.Directory) + return ret0 +} + +// BuildFormHierarchy indicates an expected call of BuildFormHierarchy +func (mr *MockStorageMockRecorder) BuildFormHierarchy(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildFormHierarchy", reflect.TypeOf((*MockStorage)(nil).BuildFormHierarchy), arg0) +} + +// 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{}) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "GetFileAsInterface", arg0, arg1) +} + +// 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) string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFilePreview", arg0) + ret0, _ := ret[0].(string) + return ret0 +} + +// GetFilePreview indicates an expected call of GetFilePreview +func (mr *MockStorageMockRecorder) GetFilePreview(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFilePreview", reflect.TypeOf((*MockStorage)(nil).GetFilePreview), arg0) +} + +// GetFileThumbnailUrl mocks base method +func (m *MockStorage) GetFileThumbnailUrl(arg0 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "GetFileThumbnailUrl", arg0) +} + +// 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_roles_repo.go b/mocks/mock_roles_repo.go new file mode 100644 index 0000000..94b0d07 --- /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) (*roles.Permissions, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPermissions", arg0) + ret0, _ := ret[0].(*roles.Permissions) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPermissions indicates an expected call of GetPermissions +func (mr *MockRolesRepoMockRecorder) GetPermissions(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPermissions", reflect.TypeOf((*MockRolesRepo)(nil).GetPermissions), arg0) +} + +// SetRolesByName mocks base method +func (m *MockRolesRepo) SetRolesByName(arg0 users.User, arg1 []string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetRolesByName", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetRolesByName indicates an expected call of SetRolesByName +func (mr *MockRolesRepoMockRecorder) SetRolesByName(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetRolesByName", reflect.TypeOf((*MockRolesRepo)(nil).SetRolesByName), arg0, arg1) +} + +// SetRolesByRoleId mocks base method +func (m *MockRolesRepo) SetRolesByRoleId(arg0 users.User, arg1 []int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetRolesByRoleId", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetRolesByRoleId indicates an expected call of SetRolesByRoleId +func (mr *MockRolesRepoMockRecorder) SetRolesByRoleId(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetRolesByRoleId", reflect.TypeOf((*MockRolesRepo)(nil).SetRolesByRoleId), arg0, arg1) +} 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_users_repo.go b/mocks/mock_users_repo.go new file mode 100644 index 0000000..c998b50 --- /dev/null +++ b/mocks/mock_users_repo.go @@ -0,0 +1,164 @@ +// 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) +} + +// CleanUp mocks base method +func (m *MockUserRepo) CleanUp() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "CleanUp") +} + +// CleanUp indicates an expected call of CleanUp +func (mr *MockUserRepoMockRecorder) CleanUp() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanUp", reflect.TypeOf((*MockUserRepo)(nil).CleanUp)) +} + +// 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) +} + +// ListAllActiveUsers mocks base method +func (m *MockUserRepo) ListAllActiveUsers() ([]int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListAllActiveUsers") + ret0, _ := ret[0].([]int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListAllActiveUsers indicates an expected call of ListAllActiveUsers +func (mr *MockUserRepoMockRecorder) ListAllActiveUsers() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAllActiveUsers", reflect.TypeOf((*MockUserRepo)(nil).ListAllActiveUsers)) +} + +// ListAllUsers mocks base method +func (m *MockUserRepo) ListAllUsers() ([]int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListAllUsers") + ret0, _ := ret[0].([]int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListAllUsers indicates an expected call of ListAllUsers +func (mr *MockUserRepoMockRecorder) ListAllUsers() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAllUsers", reflect.TypeOf((*MockUserRepo)(nil).ListAllUsers)) +} + +// 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)) +} + +// 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/user.go b/mocks/user.go new file mode 100644 index 0000000..2d47f6d --- /dev/null +++ b/mocks/user.go @@ -0,0 +1,165 @@ +// 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)) +} + +// 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) +} + +// 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/passwords/BasicHelper.go b/passwords/BasicHelper.go index 5cda88b..0b103b4 100644 --- a/passwords/BasicHelper.go +++ b/passwords/BasicHelper.go @@ -34,17 +34,9 @@ type Token struct { } //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 { //Now get the token - jwtTokenPasswordString := config.GetString("token_password") + jwtTokenPasswordString := configuration.GetString("token_password") //If it is null error if len(jwtTokenPasswordString) < 60 { diff --git a/passwords/Helper.go b/passwords/Helper.go index 6cde913..feb25a8 100644 --- a/passwords/Helper.go +++ b/passwords/Helper.go @@ -3,6 +3,8 @@ 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 diff --git a/passwords/ResetRepoSql.go b/passwords/ResetRepoSql.go index 2f79376..d2ee7a6 100644 --- a/passwords/ResetRepoSql.go +++ b/passwords/ResetRepoSql.go @@ -24,7 +24,7 @@ type ResetRepoSql struct { tableName string //We need the emailer - emailer email.Interface + emailer email.Emailer resetEmailConfig PasswordResetConfig activationEmailConfig PasswordResetConfig @@ -45,23 +45,15 @@ const ( ) //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, tableName string, emailer email.Emailer, configuration configuration.Configuration) *ResetRepoSql { //Build a reset and activation config resetEmailConfig := PasswordResetConfig{} activationEmailConfig := PasswordResetConfig{} //Pull from the config - config.GetStruct("password_reset", &resetEmailConfig) - config.GetStruct("user_activation", &activationEmailConfig) + configuration.GetStruct("password_reset", &resetEmailConfig) + configuration.GetStruct("user_activation", &activationEmailConfig) //Define a new repo newRepo := ResetRepoSql{ @@ -74,7 +66,7 @@ func NewRepoMySql(db *sql.DB, tableName string, emailer email.Interface, configF //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) )") + _, 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) } @@ -112,23 +104,14 @@ func NewRepoMySql(db *sql.DB, tableName string, emailer email.Interface, configF } //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, tableName string, emailer email.Emailer, configuration configuration.Configuration) *ResetRepoSql { //Build a reset and activation config resetEmailConfig := PasswordResetConfig{} activationEmailConfig := PasswordResetConfig{} //Pull from the config - config.GetStruct("password_reset", &resetEmailConfig) - config.GetStruct("user_activation", &activationEmailConfig) + configuration.GetStruct("password_reset", &resetEmailConfig) + configuration.GetStruct("user_activation", &activationEmailConfig) //Define a new repo newRepo := ResetRepoSql{ @@ -141,7 +124,7 @@ func NewRepoPostgresSql(db *sql.DB, tableName string, emailer email.Interface, c //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)") + _, 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) @@ -202,7 +185,7 @@ 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 @@ -231,7 +214,7 @@ 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 the user calcs return err diff --git a/roles/Permissions.go b/roles/Permissions.go index 4640805..bb3c57e 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 - } /** diff --git a/roles/Repo.go b/roles/Repo.go index f50b016..afc7fea 100644 --- a/roles/Repo.go +++ b/roles/Repo.go @@ -3,6 +3,8 @@ 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" /** diff --git a/routing/Router.go b/routing/MuxRouter.go similarity index 91% rename from routing/Router.go rename to routing/MuxRouter.go index b55c806..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 @@ -39,7 +39,7 @@ 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/users/Facebook.go b/users/Facebook.go index 3b0dcc0..c7a7b46 100644 --- a/users/Facebook.go +++ b/users/Facebook.go @@ -7,11 +7,12 @@ import ( "encoding/json" "errors" "fmt" + "net/http" + "net/url" + "github.com/reaction-eng/restlib/configuration" "github.com/reaction-eng/restlib/routing" "github.com/reaction-eng/restlib/utils" - "net/http" - "net/url" ) /** @@ -69,15 +70,12 @@ type FacebookHandler struct { /** * This struct is used */ -func NewFacebookHandler(helper *Helper, configFiles ...string) *FacebookHandler { - //Create a new config - config, _ := configuration.NewConfiguration(configFiles...) - +func NewFacebookHandler(helper *Helper, configuration configuration.Configuration) *FacebookHandler { //Create a new facebook := &FacebookHandler{ helper: helper, - clientId: config.GetString("facebook_client_id"), - clientSecret: config.GetString("facebook_client_secret"), + clientId: configuration.GetString("facebook_client_id"), + clientSecret: configuration.GetString("facebook_client_secret"), } return facebook diff --git a/users/Google.go b/users/Google.go index a391893..b0e7771 100644 --- a/users/Google.go +++ b/users/Google.go @@ -7,11 +7,12 @@ import ( "context" "encoding/json" "errors" + "net/http" + "github.com/reaction-eng/restlib/configuration" "github.com/reaction-eng/restlib/routing" "github.com/reaction-eng/restlib/utils" "golang.org/x/oauth2/google" - "net/http" "golang.org/x/oauth2" goauth2 "google.golang.org/api/oauth2/v2" @@ -39,16 +40,13 @@ type GoogleHandler struct { /** * This struct is used */ -func NewGoogleHandler(helper *Helper, configFiles ...string) *GoogleHandler { - //Create a new config - config, _ := configuration.NewConfiguration(configFiles...) - +func NewGoogleHandler(helper *Helper, configuration configuration.Configuration) *GoogleHandler { //Create a new google := &GoogleHandler{ helper: helper, oAuthConfig: &oauth2.Config{ - ClientID: config.GetStringFatal("google_client_id"), - ClientSecret: config.GetStringFatal("google_client_secret"), + ClientID: configuration.GetStringFatal("google_client_id"), + ClientSecret: configuration.GetStringFatal("google_client_secret"), Scopes: []string{"email", "profile"}, Endpoint: google.Endpoint, }, diff --git a/users/Repo.go b/users/Repo.go index 7d303c5..b954aac 100644 --- a/users/Repo.go +++ b/users/Repo.go @@ -3,6 +3,8 @@ package users +//go:generate mockgen -destination=../mocks/mock_users_repo.go -package=mocks -mock_names Repo=MockUserRepo github.com/reaction-eng/restlib/users Repo + /** Define an interface that all Calc Repos must follow */ diff --git a/users/User.go b/users/User.go index 1aab345..b324b08 100644 --- a/users/User.go +++ b/users/User.go @@ -3,6 +3,8 @@ package users +//go:generate mockgen -destination=../mocks/user.go -package=mocks github.com/reaction-eng/restlib/users User + //a struct to rep user account type User interface { //Return the user id diff --git a/users/User_test.go b/users/User_test.go index 8e68a1d..09b05e8 100644 --- a/users/User_test.go +++ b/users/User_test.go @@ -4,20 +4,21 @@ package users_test import ( + "net/http" + "net/http/httptest" + "testing" + "github.com/reaction-eng/restlib/middleware" "github.com/reaction-eng/restlib/passwords" "github.com/reaction-eng/restlib/routing" "github.com/reaction-eng/restlib/users" - "net/http" - "net/http/httptest" - "testing" ) /** Function to carray along the */ type routingEnv struct { - router *routing.Router + router *routing.MuxRouter } /** From 0f35b6029f08f3c7ec2ccffec53abc2eee6ff79f Mon Sep 17 00:00:00 2001 From: mcgurn Date: Sun, 1 Dec 2019 08:38:23 -0700 Subject: [PATCH 05/23] added tests for passwords --- go.mod | 3 +- go.sum | 9 +- passwords/{BasicHelper.go => basicHelper.go} | 21 +- passwords/basicHelper_test.go | 301 +++++++++ passwords/{Helper.go => helper.go} | 0 passwords/{ResetRepo.go => resetRepo.go} | 24 +- .../{ResetRepoSql.go => resetRepoSql.go} | 109 ++-- passwords/resetRepoSql_test.go | 577 ++++++++++++++++++ 8 files changed, 923 insertions(+), 121 deletions(-) rename passwords/{BasicHelper.go => basicHelper.go} (91%) create mode 100644 passwords/basicHelper_test.go rename passwords/{Helper.go => helper.go} (100%) rename passwords/{ResetRepo.go => resetRepo.go} (73%) rename passwords/{ResetRepoSql.go => resetRepoSql.go} (77%) create mode 100644 passwords/resetRepoSql_test.go diff --git a/go.mod b/go.mod index b938ed2..e71b68c 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/reaction-eng/restlib go 1.12 require ( + 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 @@ -22,7 +23,6 @@ require ( github.com/nlopes/slack v0.5.0 github.com/onsi/ginkgo v1.10.3 // indirect github.com/onsi/gomega v1.7.1 // indirect - github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pkg/errors v0.8.1 // indirect github.com/stretchr/testify v1.4.0 github.com/tidwall/pretty v1.0.0 // indirect @@ -33,6 +33,5 @@ require ( golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 golang.org/x/net v0.0.0-20190620200207-3b0461eec859 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 - golang.org/x/tools v0.0.0-20191122000530-004141db30c2 // indirect google.golang.org/api v0.7.0 ) diff --git a/go.sum b/go.sum index 46b1daa..34b2d1c 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +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/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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -68,8 +70,6 @@ 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.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= -github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -100,8 +100,6 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk 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-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 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= @@ -142,9 +140,6 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3 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-20191122000530-004141db30c2 h1:NyRLhayH0+K/d7asTjF9pRtOfq1G7nNsKRP3XzA3lVc= -golang.org/x/tools v0.0.0-20191122000530-004141db30c2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= diff --git a/passwords/BasicHelper.go b/passwords/basicHelper.go similarity index 91% rename from passwords/BasicHelper.go rename to passwords/basicHelper.go index 0b103b4..f4ad98e 100644 --- a/passwords/BasicHelper.go +++ b/passwords/basicHelper.go @@ -7,7 +7,6 @@ import ( "crypto/rand" "errors" "fmt" - "log" "strings" "github.com/dgrijalva/jwt-go" @@ -15,10 +14,6 @@ import ( "golang.org/x/crypto/bcrypt" ) -/** -gFile of static support functions for passwords creating, editing, hashing, etc. -*/ - type BasicHelper struct { //Keep a global password config jwtTokenPassword []byte @@ -34,20 +29,19 @@ type Token struct { } //Load it during init -func NewBasicHelper(configuration configuration.Configuration) *BasicHelper { +func NewBasicHelper(configuration configuration.Configuration) (*BasicHelper, error) { //Now get the token 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 } /** @@ -58,7 +52,6 @@ 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) - } /** @@ -87,7 +80,6 @@ func (helper *BasicHelper) ComparePasswords(currentPwHash string, testingPasswor } else { return true } - } /** @@ -99,9 +91,6 @@ func (helper *BasicHelper) TokenGenerator() string { return fmt.Sprintf("%x", b) } -/** - Compare passwords. Determine if they match -*/ func (helper *BasicHelper) ValidateToken(tokenHeader string) (int, string, error) { //Token is missing, returns with error code 403 Unauthorized @@ -138,11 +127,9 @@ func (helper *BasicHelper) ValidateToken(tokenHeader string) (int, string, error if !token.Valid { //Return the error return -1, "", errors.New("auth_forbidden") - } return tk.UserId, tk.Email, nil - } /** diff --git a/passwords/basicHelper_test.go b/passwords/basicHelper_test.go new file mode 100644 index 0000000..c82d253 --- /dev/null +++ b/passwords/basicHelper_test.go @@ -0,0 +1,301 @@ +// 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 + email string + expectedResult string + }{ + { + 42, + "example@example.com", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjQyLCJFbWFpbCI6ImV4YW1wbGVAZXhhbXBsZS5jb20ifQ.Ah3uKFRpjKpfBys0Rmq3MdSwwxkPj_WLBqBy9P21heI", + }, + { + 102, + "example2@example.com", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEwMiwiRW1haWwiOiJleGFtcGxlMkBleGFtcGxlLmNvbSJ9.tsBA8HJeN-Zhamyx6AvcTlmp5P_5ihTYsusln-50MBg", + }, + { + 98, + "matt@example.com", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjk4LCJFbWFpbCI6Im1hdHRAZXhhbXBsZS5jb20ifQ.zbpK1ZSeOTrsvSscE7KJqdhHfMgiOSHiu_2jOBsOLyA", + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + for _, testCase := range testCases { + // arrange + helper := setupBasicHelper(t, mockCtrl) + + // act + output := helper.CreateJWTToken(testCase.userId, 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 + email string + error error + }{ + { + "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjk4LCJFbWFpbCI6Im1hdHRAZXhhbXBsZS5jb20ifQ.zbpK1ZSeOTrsvSscE7KJqdhHfMgiOSHiu_2jOBsOLyA", + 98, + "matt@example.com", + nil, + }, + { + "", + -1, + "", + errors.New("auth_missing_token"), + }, + { + "Be", + -1, + "", + errors.New("auth_malformed_token"), + }, + { + "BearereyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjk4LCJFbWFpbCI6Im1hdHRAZXhhbXBsZS5jb20ifQ.zbpK1ZSeOTrsvSscE7KJqdhHfMgiOSHiu_2jOBsOLyA", + -1, + "", + errors.New("auth_malformed_token"), + }, + { + "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ2.eyJVc2VySWQiOjk4LCJFbWFpbCI6Im1hdHRAZXhhbXBsZS5jb20ifQ.zbpK1ZSeOTrsvSscE7KJqdhHfMgiOSHiu_2jOBsOLyA", + -1, + "", + errors.New("auth_malformed_token"), + }, + { + "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjk4LCJFbWFpbCI6Im1hdHRAZXhhbXBsZS5jb20ifQ.zbpK1ZSeOTrsvSscEdKJqdhHfMgiOSHiu_2jOBsOLyA", + -1, + "", + errors.New("auth_malformed_token"), + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + for _, testCase := range testCases { + // arrange + helper := setupBasicHelper(t, mockCtrl) + + // act + userId, email, err := helper.ValidateToken(testCase.token) + + // assert + assert.Equal(t, testCase.userId, userId) + 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 100% rename from passwords/Helper.go rename to passwords/helper.go diff --git a/passwords/ResetRepo.go b/passwords/resetRepo.go similarity index 73% rename from passwords/ResetRepo.go rename to passwords/resetRepo.go index 85df60d..55cfe57 100644 --- a/passwords/ResetRepo.go +++ b/passwords/resetRepo.go @@ -3,39 +3,19 @@ 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) - /** - 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 - */ UseToken(id int) error - /** - Allow databases to be closed - */ CleanUp() } diff --git a/passwords/ResetRepoSql.go b/passwords/resetRepoSql.go similarity index 77% rename from passwords/ResetRepoSql.go rename to passwords/resetRepoSql.go index d2ee7a6..32ebb75 100644 --- a/passwords/ResetRepoSql.go +++ b/passwords/resetRepoSql.go @@ -6,16 +6,12 @@ package passwords import ( "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 -*/ type ResetRepoSql struct { //Hold on to the sql databased db *sql.DB @@ -44,8 +40,7 @@ const ( reset tokenType = 2 ) -//Provide a method to make a new UserRepoSql -func NewRepoMySql(db *sql.DB, tableName string, emailer email.Emailer, configuration configuration.Configuration) *ResetRepoSql { +func NewRepoMySql(db *sql.DB, tableName string, emailer email.Emailer, configuration configuration.Configuration) (*ResetRepoSql, error) { //Build a reset and activation config resetEmailConfig := PasswordResetConfig{} @@ -66,45 +61,44 @@ func NewRepoMySql(db *sql.DB, tableName string, emailer email.Emailer, configura //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) - } + //_, 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) + //} //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 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 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.Emailer, configuration configuration.Configuration) *ResetRepoSql { +func NewRepoPostgresSql(db *sql.DB, tableName string, emailer email.Emailer, configuration configuration.Configuration) (*ResetRepoSql, error) { //Build a reset and activation config resetEmailConfig := PasswordResetConfig{} activationEmailConfig := PasswordResetConfig{} @@ -124,53 +118,48 @@ func NewRepoPostgresSql(db *sql.DB, tableName string, emailer email.Emailer, con //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) - } + //_, 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)") //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 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 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 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{ @@ -187,19 +176,17 @@ func (repo *ResetRepoSql) IssueResetRequest(token string, userId int, emailAddre //Now email 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{ @@ -216,12 +203,11 @@ func (repo *ResetRepoSql) IssueActivationRequest(token string, userId int, email //Now email err = repo.emailer.SendTemplateFile(&header, repo.activationEmailConfig.Template, resetInfo, nil) - //Return the user calcs return err } /** -Use the taken to validate +Use the token to validate */ func (repo *ResetRepoSql) CheckForResetToken(userId int, token string) (int, error) { @@ -254,22 +240,19 @@ func (repo *ResetRepoSql) CheckForActivationToken(userId int, token string) (int } -/** -Use the taken to validate -*/ 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) + err := repo.getRequestStatement.QueryRow(userId, token, tkType).Scan(&id, &userIdDb, &emailDb, &tokenDb, &issued, &tokenType) //So it was correct, check the date //TODO: check the date @@ -280,11 +263,10 @@ func (repo *ResetRepoSql) checkForToken(userId int, token string, tkType tokenTy } //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 } @@ -292,30 +274,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..c6b226c --- /dev/null +++ b/passwords/resetRepoSql_test.go @@ -0,0 +1,577 @@ +// 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() + + tableName := "resetRepo" + mock.ExpectPrepare("INSERT INTO " + tableName) + mock.ExpectPrepare("SELECT (.+) FROM " + tableName) + mock.ExpectPrepare("delete FROM " + 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" + }) + + // act + repoMySql, err := passwords.NewRepoMySql(db, tableName, 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() + + tableName := "resetRepo" + mock.ExpectPrepare("INSERT INTO " + tableName) + mock.ExpectPrepare("SELECT (.+) FROM " + tableName) + mock.ExpectPrepare("delete FROM " + 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" + }) + + // act + repoMySql, err := passwords.NewRepoPostgresSql(db, tableName, 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" + }) + + 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) + + tableName := "resetRepo" + db, dbMock, mockEmailer, mockConfiguration := setupSqlMock(t, mockCtrl, tableName) + + repo, err := passwords.NewRepoPostgresSql(db, tableName, 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 "+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) + + tableName := "resetRepo" + db, dbMock, mockEmailer, mockConfiguration := setupSqlMock(t, mockCtrl, tableName) + + repo, err := passwords.NewRepoPostgresSql(db, tableName, 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 "+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 + queryError error + rowId int + expectedRowId int + expectedError error + }{ + { + 100, + "example token", + 100, + "example token", + nil, + 1023, + 1023, + nil, + }, + { + 100, + "example token", + 100, + "other token token", + nil, + 1023, + -1, + errors.New("password_change_forbidden"), + }, + { + 100, + "example token", + 102, + "other example token", + nil, + 1023, + -1, + errors.New("password_change_forbidden"), + }, + { + 100, + "example token", + 102, + "example token", + nil, + 1023, + -1, + errors.New("password_change_forbidden"), + }, + { + 100, + "example token", + 100, + "example token", + errors.New("queryError"), + 1023, + -1, + errors.New("password_change_forbidden"), + }, + } + + for _, testCase := range testCases { + // arrange + mockCtrl := gomock.NewController(t) + + tableName := "resetRepo" + db, dbMock, mockEmailer, mockConfiguration := setupSqlMock(t, mockCtrl, tableName) + + repo, err := passwords.NewRepoPostgresSql(db, tableName, mockEmailer, mockConfiguration) + + rows := sqlmock.NewRows([]string{"id", "userId", "email", "token", "issued", "type"}). + AddRow(testCase.rowId, testCase.userIdDb, "email", testCase.tokenDb, time.Now(), 1) + + dbMock.ExpectQuery("SELECT (.+) FROM " + 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 + queryError error + rowId int + expectedRowId int + expectedError error + }{ + { + 100, + "example token", + 100, + "example token", + nil, + 1023, + 1023, + nil, + }, + { + 100, + "example token", + 100, + "other token token", + nil, + 1023, + -1, + errors.New("activation_forbidden"), + }, + { + 100, + "example token", + 102, + "other example token", + nil, + 1023, + -1, + errors.New("activation_forbidden"), + }, + { + 100, + "example token", + 102, + "example token", + nil, + 1023, + -1, + errors.New("activation_forbidden"), + }, + { + 100, + "example token", + 100, + "example token", + errors.New("queryError"), + 1023, + -1, + errors.New("activation_forbidden"), + }, + } + + for _, testCase := range testCases { + // arrange + mockCtrl := gomock.NewController(t) + + tableName := "resetRepo" + db, dbMock, mockEmailer, mockConfiguration := setupSqlMock(t, mockCtrl, tableName) + + repo, err := passwords.NewRepoPostgresSql(db, tableName, mockEmailer, mockConfiguration) + + rows := sqlmock.NewRows([]string{"id", "userId", "email", "token", "issued", "type"}). + AddRow(testCase.rowId, testCase.userIdDb, "email", testCase.tokenDb, time.Now(), 2) + + dbMock.ExpectQuery("SELECT (.+) FROM " + 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_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) + + tableName := "resetRepo" + db, dbMock, mockEmailer, mockConfiguration := setupSqlMock(t, mockCtrl, tableName) + + repo, _ := passwords.NewRepoPostgresSql(db, tableName, mockEmailer, mockConfiguration) + + dbMock.ExpectExec("delete FROM " + 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) + + tableName := "resetRepo" + + 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 " + tableName).WillBeClosed() + dbMock.ExpectPrepare("SELECT (.+) FROM " + tableName).WillBeClosed() + dbMock.ExpectPrepare("delete FROM " + 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" + }) + + repo, _ := passwords.NewRepoPostgresSql(db, tableName, mockEmailer, mockConfiguration) + + // act + repo.CleanUp() + + // assert + if err := dbMock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expectations: %s", err) + } +} From 8b18f27459c8819bacaf3ce8d393fd09c5b67807 Mon Sep 17 00:00:00 2001 From: mcgurn Date: Sun, 1 Dec 2019 10:32:49 -0700 Subject: [PATCH 06/23] added tests for passwords and resetRequest db migration (see readme) --- README.md | 22 +++++++- go.mod | 2 + go.sum | 95 ++++++++++++++++++++++++++++++++++ migrations/migrations.go | 18 +++++++ mocks/mock_helper.go | 5 +- passwords/basicHelper.go | 2 +- passwords/helper.go | 2 +- passwords/resetRepo.go | 2 +- passwords/resetRepoSql.go | 36 ++++--------- passwords/resetRepoSql_test.go | 63 ++++++++++------------ users/User_test.go | 2 +- 11 files changed, 181 insertions(+), 68 deletions(-) create mode 100644 migrations/migrations.go diff --git a/README.md b/README.md index 7afa770..a090196 100644 --- a/README.md +++ b/README.md @@ -22,4 +22,24 @@ go generate ./... ## Testing ## -The Framework uses the builtin testing capabilities in go \ No newline at end of file +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/go.mod b/go.mod index e71b68c..58e7cac 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/go-redis/redis v6.15.6+incompatible github.com/go-sql-driver/mysql v1.4.1 github.com/go-stack/stack v1.8.0 // indirect + github.com/gobuffalo/packr/v2 v2.7.1 github.com/golang/mock v1.3.1 github.com/golang/snappy v0.0.1 // indirect github.com/gorilla/mux v1.7.3 @@ -24,6 +25,7 @@ require ( github.com/onsi/ginkgo v1.10.3 // indirect github.com/onsi/gomega v1.7.1 // indirect github.com/pkg/errors v0.8.1 // indirect + github.com/rubenv/sql-migrate v0.0.0-20191121092708-da1cb182f00e github.com/stretchr/testify v1.4.0 github.com/tidwall/pretty v1.0.0 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect diff --git a/go.sum b/go.sum index 34b2d1c..bcf8fea 100644 --- a/go.sum +++ b/go.sum @@ -7,13 +7,24 @@ github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFD github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= 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/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +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/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 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/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/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/simplify v0.0.0-20170216171241-d32f302d5046 h1:n3RPbpwXSFT0G8FYslzMUBDO09Ix8/dlqzvUkcJm4Jk= @@ -28,6 +39,16 @@ github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZp github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 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/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/logger v1.0.1 h1:ZEgyRGgAm4ZAhAO45YXMs5Fp+bzGLESFewzAVBMKuTg= +github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +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.7.1 h1:n3CIW5T17T8v4GGK5sWXLVWJhCz7b5aNLSxW6gYim4o= +github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= +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/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -51,46 +72,101 @@ 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/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 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/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +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/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/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/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +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/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 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/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +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.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 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/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/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/rogpeppe/go-internal v1.1.0/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/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +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.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.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 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/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 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 v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= +github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.mongodb.org/mongo-driver v1.0.4 h1:bHxbjH6iwh1uInchXadI6hQR107KEbgYsMzoblDONmQ= go.mongodb.org/mongo-driver v1.0.4/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +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-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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -109,6 +185,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/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-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -121,12 +198,19 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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/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-20180909124046-d0be0721c37e/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/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/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= @@ -140,6 +224,8 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3 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-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= @@ -147,6 +233,8 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl 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.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= +google.golang.org/appengine v1.6.5/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= @@ -157,14 +245,21 @@ google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 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/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/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw= +gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= 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/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-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= diff --git a/migrations/migrations.go b/migrations/migrations.go new file mode 100644 index 0000000..28f5814 --- /dev/null +++ b/migrations/migrations.go @@ -0,0 +1,18 @@ +package migrations + +import ( + "github.com/gobuffalo/packr/v2" + migrate "github.com/rubenv/sql-migrate" +) + +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/mocks/mock_helper.go b/mocks/mock_helper.go index 93f1b81..5d0c3ee 100644 --- a/mocks/mock_helper.go +++ b/mocks/mock_helper.go @@ -1,12 +1,13 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/reaction-eng/restlib/passwords (interfaces: Helper) +// Source: github.com/reaction-eng/restlib/mysql (interfaces: Helper) // Package mocks is a generated GoMock package. package mocks import ( - gomock "github.com/golang/mock/gomock" reflect "reflect" + + gomock "github.com/golang/mock/gomock" ) // MockHelper is a mock of Helper interface diff --git a/passwords/basicHelper.go b/passwords/basicHelper.go index f4ad98e..2552d9e 100644 --- a/passwords/basicHelper.go +++ b/passwords/basicHelper.go @@ -69,7 +69,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 { diff --git a/passwords/helper.go b/passwords/helper.go index feb25a8..461ddef 100644 --- a/passwords/helper.go +++ b/passwords/helper.go @@ -3,7 +3,7 @@ package passwords -//go:generate mockgen -destination=../mocks/mock_helper.go -package=mocks github.com/reaction-eng/restlib/passwords Helper +//go:generate mockgen -destination=../mocks/mock_helper.go -package=mocks github.com/reaction-eng/restlib/mysql Helper type Helper interface { HashPassword(password string) string diff --git a/passwords/resetRepo.go b/passwords/resetRepo.go index 55cfe57..d34de65 100644 --- a/passwords/resetRepo.go +++ b/passwords/resetRepo.go @@ -3,7 +3,7 @@ package passwords -//go:generate mockgen -destination=../mocks/mock_resetRepo.go -package=mocks github.com/reaction-eng/restlib/passwords ResetRepo +//go:generate mockgen -destination=../mocks/mock_resetRepo.go -package=mocks github.com/reaction-eng/restlib/mysql ResetRepo type ResetRepo interface { IssueResetRequest(token string, userId int, email string) error diff --git a/passwords/resetRepoSql.go b/passwords/resetRepoSql.go index 32ebb75..8d68bbb 100644 --- a/passwords/resetRepoSql.go +++ b/passwords/resetRepoSql.go @@ -12,19 +12,18 @@ import ( "github.com/reaction-eng/restlib/email" ) +const TableName = "resetrequests" + 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.Emailer resetEmailConfig PasswordResetConfig activationEmailConfig 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 @@ -40,7 +39,7 @@ const ( reset tokenType = 2 ) -func NewRepoMySql(db *sql.DB, tableName string, emailer email.Emailer, configuration configuration.Configuration) (*ResetRepoSql, error) { +func NewRepoMySql(db *sql.DB, emailer email.Emailer, configuration configuration.Configuration) (*ResetRepoSql, error) { //Build a reset and activation config resetEmailConfig := PasswordResetConfig{} @@ -53,21 +52,13 @@ func NewRepoMySql(db *sql.DB, tableName string, emailer email.Emailer, configura //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) - //} - //Add request data to table - addRequest, err := db.Prepare("INSERT INTO " + tableName + " (userId,email, token, issued, type) VALUES (?, ?, ?, ?, ?)") + addRequest, err := db.Prepare("INSERT INTO " + TableName + " (userId,email, token, issued, type) VALUES (?, ?, ?, ?, ?)") if err != nil { return nil, err } @@ -76,7 +67,7 @@ func NewRepoMySql(db *sql.DB, tableName string, emailer email.Emailer, configura newRepo.addRequestStatement = addRequest //pull the request from the table - getRequest, err := db.Prepare("SELECT * FROM " + tableName + " where userId = ? AND token = ? AND type = ?") + getRequest, err := db.Prepare("SELECT * FROM " + TableName + " where userId = ? AND token = ? AND type = ?") if err != nil { return nil, err } @@ -85,7 +76,7 @@ func NewRepoMySql(db *sql.DB, tableName string, emailer email.Emailer, configura newRepo.getRequestStatement = getRequest //pull the request from the table - rmRequest, err := db.Prepare("delete FROM " + tableName + " where id = ? limit 1") + rmRequest, err := db.Prepare("delete FROM " + TableName + " where id = ? limit 1") if err != nil { return nil, err } @@ -98,7 +89,7 @@ func NewRepoMySql(db *sql.DB, tableName string, emailer email.Emailer, configura } -func NewRepoPostgresSql(db *sql.DB, tableName string, emailer email.Emailer, configuration configuration.Configuration) (*ResetRepoSql, error) { +func NewRepoPostgresSql(db *sql.DB, emailer email.Emailer, configuration configuration.Configuration) (*ResetRepoSql, error) { //Build a reset and activation config resetEmailConfig := PasswordResetConfig{} activationEmailConfig := PasswordResetConfig{} @@ -110,18 +101,13 @@ func NewRepoPostgresSql(db *sql.DB, tableName string, emailer email.Emailer, con //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)") - //Add request data to table - addRequest, err := db.Prepare("INSERT INTO " + tableName + "(userId,email, token, issued, type) VALUES ($1, $2, $3, $4, $5)") + addRequest, err := db.Prepare("INSERT INTO " + TableName + "(userId,email, token, issued, type) VALUES ($1, $2, $3, $4, $5)") if err != nil { return nil, err } @@ -130,7 +116,7 @@ func NewRepoPostgresSql(db *sql.DB, tableName string, emailer email.Emailer, con 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") + getRequest, err := db.Prepare("SELECT * FROM " + TableName + " where userId = $1 AND token = $2 AND type = $3") if err != nil { return nil, err } @@ -139,7 +125,7 @@ func NewRepoPostgresSql(db *sql.DB, tableName string, emailer email.Emailer, con newRepo.getRequestStatement = getRequest //pull the request from the table - rmRequest, err := db.Prepare("delete FROM " + tableName + " where id = $1") + rmRequest, err := db.Prepare("delete FROM " + TableName + " where id = $1") if err != nil { return nil, err } diff --git a/passwords/resetRepoSql_test.go b/passwords/resetRepoSql_test.go index c6b226c..3f6f508 100644 --- a/passwords/resetRepoSql_test.go +++ b/passwords/resetRepoSql_test.go @@ -29,10 +29,9 @@ func TestNewRepoMySql(t *testing.T) { } defer db.Close() - tableName := "resetRepo" - mock.ExpectPrepare("INSERT INTO " + tableName) - mock.ExpectPrepare("SELECT (.+) FROM " + tableName) - mock.ExpectPrepare("delete FROM " + tableName) + 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() @@ -51,7 +50,7 @@ func TestNewRepoMySql(t *testing.T) { }) // act - repoMySql, err := passwords.NewRepoMySql(db, tableName, mockEmailer, mockConfiguration) + repoMySql, err := passwords.NewRepoMySql(db, mockEmailer, mockConfiguration) // assert assert.Nil(t, err) @@ -66,10 +65,9 @@ func TestNewRepoPostgresSql(t *testing.T) { } defer db.Close() - tableName := "resetRepo" - mock.ExpectPrepare("INSERT INTO " + tableName) - mock.ExpectPrepare("SELECT (.+) FROM " + tableName) - mock.ExpectPrepare("delete FROM " + tableName) + 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() @@ -88,7 +86,7 @@ func TestNewRepoPostgresSql(t *testing.T) { }) // act - repoMySql, err := passwords.NewRepoPostgresSql(db, tableName, mockEmailer, mockConfiguration) + repoMySql, err := passwords.NewRepoPostgresSql(db, mockEmailer, mockConfiguration) // assert assert.Nil(t, err) @@ -171,10 +169,9 @@ func TestResetRepoSql_IssueResetRequest(t *testing.T) { // arrange mockCtrl := gomock.NewController(t) - tableName := "resetRepo" - db, dbMock, mockEmailer, mockConfiguration := setupSqlMock(t, mockCtrl, tableName) + db, dbMock, mockEmailer, mockConfiguration := setupSqlMock(t, mockCtrl, passwords.TableName) - repo, err := passwords.NewRepoPostgresSql(db, tableName, mockEmailer, mockConfiguration) + repo, err := passwords.NewRepoPostgresSql(db, mockEmailer, mockConfiguration) emailHeader := email.HeaderInfo{ Subject: "password_reset_subject", @@ -190,7 +187,7 @@ func TestResetRepoSql_IssueResetRequest(t *testing.T) { mockEmailer.EXPECT().SendTemplateFile(&emailHeader, "password_reset_template", resetInfo, nil).Times(1).Return(testCase.emailError) } - dbMock.ExpectExec("INSERT INTO "+tableName). + dbMock.ExpectExec("INSERT INTO "+passwords.TableName). WithArgs(testCase.userId, testCase.emailAddress, testCase.token, AnyTime{}, 2). WillReturnResult(sqlmock.NewResult(0, 0)). WillReturnError(testCase.execError) @@ -258,10 +255,9 @@ func TestResetRepoSql_IssueActivationRequest(t *testing.T) { // arrange mockCtrl := gomock.NewController(t) - tableName := "resetRepo" - db, dbMock, mockEmailer, mockConfiguration := setupSqlMock(t, mockCtrl, tableName) + db, dbMock, mockEmailer, mockConfiguration := setupSqlMock(t, mockCtrl, passwords.TableName) - repo, err := passwords.NewRepoPostgresSql(db, tableName, mockEmailer, mockConfiguration) + repo, err := passwords.NewRepoPostgresSql(db, mockEmailer, mockConfiguration) emailHeader := email.HeaderInfo{ Subject: "user_activation_subject", @@ -277,7 +273,7 @@ func TestResetRepoSql_IssueActivationRequest(t *testing.T) { mockEmailer.EXPECT().SendTemplateFile(&emailHeader, "user_activation_template", resetInfo, nil).Times(1).Return(testCase.emailError) } - dbMock.ExpectExec("INSERT INTO "+tableName). + 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) @@ -370,15 +366,14 @@ func TestResetRepoSql_CheckForResetToken(t *testing.T) { // arrange mockCtrl := gomock.NewController(t) - tableName := "resetRepo" - db, dbMock, mockEmailer, mockConfiguration := setupSqlMock(t, mockCtrl, tableName) + db, dbMock, mockEmailer, mockConfiguration := setupSqlMock(t, mockCtrl, passwords.TableName) - repo, err := passwords.NewRepoPostgresSql(db, tableName, mockEmailer, mockConfiguration) + 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, time.Now(), 1) - dbMock.ExpectQuery("SELECT (.+) FROM " + tableName). + dbMock.ExpectQuery("SELECT (.+) FROM " + passwords.TableName). WillReturnRows(rows). WillReturnError(testCase.queryError) @@ -465,15 +460,14 @@ func TestResetRepoSql_CheckForActivationToken(t *testing.T) { // arrange mockCtrl := gomock.NewController(t) - tableName := "resetRepo" - db, dbMock, mockEmailer, mockConfiguration := setupSqlMock(t, mockCtrl, tableName) + db, dbMock, mockEmailer, mockConfiguration := setupSqlMock(t, mockCtrl, passwords.TableName) - repo, err := passwords.NewRepoPostgresSql(db, tableName, mockEmailer, mockConfiguration) + 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, time.Now(), 2) - dbMock.ExpectQuery("SELECT (.+) FROM " + tableName). + dbMock.ExpectQuery("SELECT (.+) FROM " + passwords.TableName). WillReturnRows(rows). WillReturnError(testCase.queryError) @@ -512,12 +506,11 @@ func TestResetRepoSql_UseToken(t *testing.T) { // arrange mockCtrl := gomock.NewController(t) - tableName := "resetRepo" - db, dbMock, mockEmailer, mockConfiguration := setupSqlMock(t, mockCtrl, tableName) + db, dbMock, mockEmailer, mockConfiguration := setupSqlMock(t, mockCtrl, passwords.TableName) - repo, _ := passwords.NewRepoPostgresSql(db, tableName, mockEmailer, mockConfiguration) + repo, _ := passwords.NewRepoPostgresSql(db, mockEmailer, mockConfiguration) - dbMock.ExpectExec("delete FROM " + tableName). + dbMock.ExpectExec("delete FROM " + passwords.TableName). WithArgs(testCase.tokenId). // one for activation WillReturnResult(sqlmock.NewResult(0, 0)). WillReturnError(testCase.error) @@ -541,16 +534,14 @@ func TestResetRepoSql_CleanUp(t *testing.T) { // arrange mockCtrl := gomock.NewController(t) - tableName := "resetRepo" - 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 " + tableName).WillBeClosed() - dbMock.ExpectPrepare("SELECT (.+) FROM " + tableName).WillBeClosed() - dbMock.ExpectPrepare("delete FROM " + tableName).WillBeClosed() + 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) @@ -565,7 +556,7 @@ func TestResetRepoSql_CleanUp(t *testing.T) { as.Template = "test email template" }) - repo, _ := passwords.NewRepoPostgresSql(db, tableName, mockEmailer, mockConfiguration) + repo, _ := passwords.NewRepoPostgresSql(db, mockEmailer, mockConfiguration) // act repo.CleanUp() diff --git a/users/User_test.go b/users/User_test.go index 09b05e8..26b218c 100644 --- a/users/User_test.go +++ b/users/User_test.go @@ -123,7 +123,7 @@ func getDefaultEnv(t *testing.T) *routingEnv { //Add in middleware/filter that respons to CORS router.Use(middleware.MakeCORSMiddlewareFunc()) //Make sure to add the cross site permission first - //Add in middleware/filter that checks for user passwords + //Add in middleware/filter that checks for user mysql router.Use(middleware.MakeJwtMiddlewareFunc(router, userRepo, nil, passHelper)) //Define the routing env From 8448b28950282f635140997a375855cc6ff6c10e Mon Sep 17 00:00:00 2001 From: mcgurn Date: Sun, 1 Dec 2019 11:26:59 -0700 Subject: [PATCH 07/23] added sql migrations --- .gitignore | 1 - migrations/mysql/1_initial_passwordReset.sql | 6 ++++++ migrations/postgres/1_initial_passwordReset.sql | 5 +++++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 migrations/mysql/1_initial_passwordReset.sql create mode 100644 migrations/postgres/1_initial_passwordReset.sql 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/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/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 From f4d55ccc0502ed0b6d6d69e3a54731fd3dff1d36 Mon Sep 17 00:00:00 2001 From: mcgurn Date: Sun, 26 Apr 2020 16:42:59 -0600 Subject: [PATCH 08/23] resume commit --- configuration/json.go | 2 +- configuration/json_test.go | 4 +- middleware/jwtHandler_test.go | 1 - mocks/mock_helper.go | 5 +- mocks/testRouter.go | 42 ++++++ mocks/user.go | 165 ----------------------- passwords/helper.go | 2 +- passwords/resetRepo.go | 2 +- preferences/Repo.go | 2 + preferences/{Handlers.go => handlers.go} | 40 +++--- preferences/handlers_test.go | 79 +++++++++++ static/Repo.go | 3 - static/RepoCache.go | 19 +-- users/User.go | 2 +- users/User_test.go | 5 +- 15 files changed, 156 insertions(+), 217 deletions(-) create mode 100644 mocks/testRouter.go delete mode 100644 mocks/user.go rename preferences/{Handlers.go => handlers.go} (84%) create mode 100644 preferences/handlers_test.go diff --git a/configuration/json.go b/configuration/json.go index 281ed4c..6498165 100644 --- a/configuration/json.go +++ b/configuration/json.go @@ -170,7 +170,7 @@ func (jsonConfig *Json) GetKeys() []string { } -func (jsonConfig *Json) GetConfig(key string) *Json { +func (jsonConfig *Json) GetConfig(key string) Configuration { //Get the child interface childConfigInterface := jsonConfig.Get(key) diff --git a/configuration/json_test.go b/configuration/json_test.go index 1ed8f8d..507d4d0 100644 --- a/configuration/json_test.go +++ b/configuration/json_test.go @@ -336,7 +336,9 @@ func TestJson_GetConfig(t *testing.T) { if result == nil { assert.Equal(t, expected, result) } else { - assert.Equal(t, expected.params, result.params) + resultJson, ok := result.(*Json) + assert.True(t, ok) + assert.Equal(t, expected.params, resultJson.params) } } } diff --git a/middleware/jwtHandler_test.go b/middleware/jwtHandler_test.go index 402a510..d022773 100644 --- a/middleware/jwtHandler_test.go +++ b/middleware/jwtHandler_test.go @@ -402,7 +402,6 @@ func TestMakeJwtMiddlewareFunc(t *testing.T) { // assert if testCase.next { - for k, v := range testCase.context { ctx := context.WithValue(r.Context(), k, v) r = r.WithContext(ctx) diff --git a/mocks/mock_helper.go b/mocks/mock_helper.go index 5d0c3ee..93f1b81 100644 --- a/mocks/mock_helper.go +++ b/mocks/mock_helper.go @@ -1,13 +1,12 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/reaction-eng/restlib/mysql (interfaces: Helper) +// Source: github.com/reaction-eng/restlib/passwords (interfaces: Helper) // Package mocks is a generated GoMock package. package mocks import ( - reflect "reflect" - gomock "github.com/golang/mock/gomock" + reflect "reflect" ) // MockHelper is a mock of Helper interface diff --git a/mocks/testRouter.go b/mocks/testRouter.go new file mode 100644 index 0000000..509d293 --- /dev/null +++ b/mocks/testRouter.go @@ -0,0 +1,42 @@ +package mocks + +import ( + "context" + "net/http" + "strings" + + "github.com/reaction-eng/restlib/routing" + "github.com/reaction-eng/restlib/users" +) + +type TestRouter struct { + routes []routing.Route + user users.User +} + +func NewTestRouter(routerProducer routing.RouteProducer, user users.User) *TestRouter { + return &TestRouter{ + routerProducer.GetRoutes(), + user, + } +} + +func (router *TestRouter) Handle(w http.ResponseWriter, r *http.Request) *routing.Route { + // Update the context + if router.user != nil { + ctx := context.WithValue(r.Context(), "user", router.user.Id()) + r = r.WithContext(ctx) + } + + uri := r.URL.Path + method := r.Method + + for _, route := range router.routes { + if strings.EqualFold(route.Pattern, uri) && strings.EqualFold(route.Method, method) { + route.HandlerFunc(w, r) + + return &route + } + } + return nil +} diff --git a/mocks/user.go b/mocks/user.go deleted file mode 100644 index 2d47f6d..0000000 --- a/mocks/user.go +++ /dev/null @@ -1,165 +0,0 @@ -// 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)) -} - -// 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) -} - -// 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/passwords/helper.go b/passwords/helper.go index 461ddef..feb25a8 100644 --- a/passwords/helper.go +++ b/passwords/helper.go @@ -3,7 +3,7 @@ package passwords -//go:generate mockgen -destination=../mocks/mock_helper.go -package=mocks github.com/reaction-eng/restlib/mysql Helper +//go:generate mockgen -destination=../mocks/mock_helper.go -package=mocks github.com/reaction-eng/restlib/passwords Helper type Helper interface { HashPassword(password string) string diff --git a/passwords/resetRepo.go b/passwords/resetRepo.go index d34de65..55cfe57 100644 --- a/passwords/resetRepo.go +++ b/passwords/resetRepo.go @@ -3,7 +3,7 @@ package passwords -//go:generate mockgen -destination=../mocks/mock_resetRepo.go -package=mocks github.com/reaction-eng/restlib/mysql ResetRepo +//go:generate mockgen -destination=../mocks/mock_resetRepo.go -package=mocks github.com/reaction-eng/restlib/passwords ResetRepo type ResetRepo interface { IssueResetRequest(token string, userId int, email string) error diff --git a/preferences/Repo.go b/preferences/Repo.go index 5b52109..523d80f 100644 --- a/preferences/Repo.go +++ b/preferences/Repo.go @@ -3,6 +3,8 @@ 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" ) diff --git a/preferences/Handlers.go b/preferences/handlers.go similarity index 84% rename from preferences/Handlers.go rename to preferences/handlers.go index 94c63a3..48fe5da 100644 --- a/preferences/Handlers.go +++ b/preferences/handlers.go @@ -5,40 +5,32 @@ package preferences import ( "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" - "io/ioutil" - "net/http" ) -/** - * 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{ @@ -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 @@ -77,8 +72,7 @@ func (handler *Handler) handleUserPreferencesGet(w http.ResponseWriter, r *http. 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 { @@ -89,9 +83,6 @@ func (handler *Handler) handleUserPreferencesGet(w http.ResponseWriter, r *http. } -/** -*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 @@ -118,8 +109,7 @@ func (handler *Handler) handleUserPreferencesSet(w http.ResponseWriter, r *http. 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 { diff --git a/preferences/handlers_test.go b/preferences/handlers_test.go new file mode 100644 index 0000000..9d56ac7 --- /dev/null +++ b/preferences/handlers_test.go @@ -0,0 +1,79 @@ +// 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 preferences_test + +import ( + "net/http" + "net/http/httptest" + "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 { + user func() users.User + status int + response string + }{ + /*{ + func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Return(101).Times(1) + + return user + }, + },*/ + { + func() users.User { return nil }, + http.StatusForbidden, + "{\"message\":\"no_user_logged_in\",\"status\":false}\n", + }, + } + + // arrange + for _, testCase := range testCases { + mockUserRepo := mocks.NewMockUserRepo(mockCtrl) + mockPrefRepo := mocks.NewMockPreferencesRepo(mockCtrl) + + handler := preferences.NewHandler(mockUserRepo, mockPrefRepo) + + router := mocks.NewTestRouter(handler, testCase.user()) + + 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.status, w.Result().StatusCode) + assert.Equal(t, testCase.response, w.Body.String()) + } + +} 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 9526269..7b18192 100644 --- a/static/RepoCache.go +++ b/static/RepoCache.go @@ -6,31 +6,22 @@ package static import ( "github.com/reaction-eng/restlib/cache" "github.com/reaction-eng/restlib/configuration" + "github.com/reaction-eng/restlib/file" ) -/** -Define a struct for RepoMem for news -*/ type RepoCache struct { //Store the cache cas cache.Cache - //We also need googl - drive *google.gDrive + 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.gDrive, cas cache.Cache, privateConfigFile string, publicConfigFile string) *RepoCache { - - //Create a new config - privateConfig, _ := configuration.NewConfiguration(privateConfigFile) - publicConfig, _ := configuration.NewConfiguration(publicConfigFile) +func NewRepoCache(drive file.Storage, cas cache.Cache, privateConfig configuration.Configuration, publicConfig configuration.Configuration) *RepoCache { - //Define a new repo newRepo := RepoCache{ cas: cas, drive: drive, diff --git a/users/User.go b/users/User.go index b324b08..d55f6f3 100644 --- a/users/User.go +++ b/users/User.go @@ -3,7 +3,7 @@ package users -//go:generate mockgen -destination=../mocks/user.go -package=mocks github.com/reaction-eng/restlib/users User +//go:generate mockgen -destination=../mocks/mock_user.go -package=mocks github.com/reaction-eng/restlib/users User //a struct to rep user account type User interface { diff --git a/users/User_test.go b/users/User_test.go index 26b218c..b65eaf9 100644 --- a/users/User_test.go +++ b/users/User_test.go @@ -8,6 +8,8 @@ import ( "net/http/httptest" "testing" + "github.com/reaction-eng/restlib/configuration" + "github.com/reaction-eng/restlib/middleware" "github.com/reaction-eng/restlib/passwords" "github.com/reaction-eng/restlib/routing" @@ -93,7 +95,8 @@ func getDefaultEnv(t *testing.T) *routingEnv { userRepo := users.NewRepoMemory() //Make a basic - passHelper := passwords.NewBasicHelper(configString) + config, _ := configuration.NewJson(configString) + passHelper, _ := passwords.NewBasicHelper(config) //Make a user helper helper := users.NewUserHelper(userRepo, nil, passHelper) From 4721bd67d81aaac0bf1f10097b88fb0a12f44d23 Mon Sep 17 00:00:00 2001 From: mcgurn Date: Sun, 26 Apr 2020 18:50:01 -0600 Subject: [PATCH 09/23] added tests for preference handlers --- go.mod | 6 +- go.sum | 281 ++++++++++++++++++++++++++++++++++ mocks/mock_preferencesRepo.go | 77 ++++++++++ mocks/mock_resetRepo.go | 117 ++++++++++++++ mocks/mock_user.go | 165 ++++++++++++++++++++ preferences/handlers.go | 20 ++- preferences/handlers_test.go | 211 +++++++++++++++++++++++-- 7 files changed, 850 insertions(+), 27 deletions(-) create mode 100644 mocks/mock_preferencesRepo.go create mode 100644 mocks/mock_resetRepo.go create mode 100644 mocks/mock_user.go diff --git a/go.mod b/go.mod index 58e7cac..5304451 100644 --- a/go.mod +++ b/go.mod @@ -25,15 +25,15 @@ require ( github.com/onsi/ginkgo v1.10.3 // indirect github.com/onsi/gomega v1.7.1 // indirect github.com/pkg/errors v0.8.1 // indirect - github.com/rubenv/sql-migrate v0.0.0-20191121092708-da1cb182f00e + github.com/rubenv/sql-migrate v0.0.0-20200423171638-eef9d3b68125 github.com/stretchr/testify v1.4.0 github.com/tidwall/pretty v1.0.0 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // 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/net v0.0.0-20190620200207-3b0461eec859 + golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 + golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 google.golang.org/api v0.7.0 ) diff --git a/go.sum b/go.sum index bcf8fea..3249014 100644 --- a/go.sum +++ b/go.sum @@ -5,36 +5,89 @@ cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSR github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 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/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/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.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +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/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-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-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= @@ -48,9 +101,19 @@ 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.7.1 h1:n3CIW5T17T8v4GGK5sWXLVWJhCz7b5aNLSxW6gYim4o= github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= +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/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= @@ -59,34 +122,80 @@ github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFU 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/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/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/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/websocket v0.0.0-20170926233335-4201258b820c/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/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/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/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/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 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= @@ -94,36 +203,109 @@ 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/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/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/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/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/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.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -131,15 +313,32 @@ github.com/rogpeppe/go-internal v1.4.0 h1:LUa41nrWTQNGhzdsZ5lTnkwbNjj6rXTdazA1cS 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.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= @@ -148,27 +347,49 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy 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 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/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.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.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= +go.opencensus.io v0.22.2/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-20190510104115-cbcb75029529/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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 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= @@ -176,18 +397,29 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk 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-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 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-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-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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= @@ -202,34 +434,59 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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-20190412213103-97732733099d/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-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-20191220142924-d4481acd189f h1:68K/z8GLUxV76xGSqwTWw2gyk/jwn79LUL43rES2g8o= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-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-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-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-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-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/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-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 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/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= @@ -238,28 +495,52 @@ google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID 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-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +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.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.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +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= +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/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..866e280 --- /dev/null +++ b/mocks/mock_resetRepo.go @@ -0,0 +1,117 @@ +// 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) +} + +// 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) +} + +// 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_user.go b/mocks/mock_user.go new file mode 100644 index 0000000..2d47f6d --- /dev/null +++ b/mocks/mock_user.go @@ -0,0 +1,165 @@ +// 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)) +} + +// 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) +} + +// 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/preferences/handlers.go b/preferences/handlers.go index 48fe5da..3f8b90a 100644 --- a/preferences/handlers.go +++ b/preferences/handlers.go @@ -34,13 +34,13 @@ func NewHandler(userRepo users.Repo, prefRepo Repo) *Handler { 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", @@ -68,7 +68,7 @@ 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 } @@ -78,7 +78,7 @@ func (handler *Handler) handleUserPreferencesGet(w http.ResponseWriter, r *http. 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()) } } @@ -86,6 +86,11 @@ func (handler *Handler) handleUserPreferencesGet(w http.ResponseWriter, r *http. 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 @@ -93,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 @@ -105,7 +109,7 @@ 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 } @@ -115,7 +119,7 @@ func (handler *Handler) handleUserPreferencesSet(w http.ResponseWriter, r *http. 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 index 9d56ac7..b1f75df 100644 --- a/preferences/handlers_test.go +++ b/preferences/handlers_test.go @@ -1,11 +1,11 @@ -// 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 preferences_test import ( + "errors" + "io" "net/http" "net/http/httptest" + "strings" "testing" "github.com/reaction-eng/restlib/users" @@ -34,29 +34,74 @@ func TestHandler_handleUserPreferencesGet(t *testing.T) { defer mockCtrl.Finish() testCases := []struct { - user func() users.User - status int - response string + comment string + user func() users.User + userError error + preferences *preferences.Preferences + preferencesError error + expectedStatus int + expectedResponse string }{ - /*{ - func() users.User { + { + comment: "with user error", + user: func() users.User { user := mocks.NewMockUser(mockCtrl) - user.EXPECT().Id().Return(101).Times(1) - + 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", + }, { - func() users.User { return nil }, - http.StatusForbidden, - "{\"message\":\"no_user_logged_in\",\"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) @@ -72,8 +117,142 @@ func TestHandler_handleUserPreferencesGet(t *testing.T) { assert.NotNil(t, route) assert.Equal(t, false, route.Public) assert.Nil(t, route.ReqPermissions) - assert.Equal(t, testCase.status, w.Result().StatusCode) - assert.Equal(t, testCase.response, w.Body.String()) + 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.NewTestRouter(handler, testCase.user()) + + 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()) } } From f4fef7d896b1c994552f4d12488ad7a082163120 Mon Sep 17 00:00:00 2001 From: mcgurn Date: Sun, 26 Apr 2020 19:10:19 -0600 Subject: [PATCH 10/23] fixed bug in json_test --- configuration/json_test.go | 6 +- go.mod | 31 +++-- go.sum | 241 +++++++++++++++++++++++++++++++++++++ 3 files changed, 264 insertions(+), 14 deletions(-) diff --git a/configuration/json_test.go b/configuration/json_test.go index 507d4d0..d7c52fa 100644 --- a/configuration/json_test.go +++ b/configuration/json_test.go @@ -329,16 +329,18 @@ func TestJson_GetConfig(t *testing.T) { result := jsonConfig.GetConfig(testCase.key) // assert - var expected *Json + 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, expected.params, resultJson.params) + assert.Equal(t, expectedJson.params, resultJson.params) } } } diff --git a/go.mod b/go.mod index 5304451..40373a0 100644 --- a/go.mod +++ b/go.mod @@ -3,37 +3,44 @@ 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-redis/cache v6.4.0+incompatible github.com/go-redis/redis v6.15.6+incompatible - github.com/go-sql-driver/mysql v1.4.1 + github.com/go-sql-driver/mysql v1.5.0 github.com/go-stack/stack v1.8.0 // indirect github.com/gobuffalo/packr/v2 v2.7.1 - github.com/golang/mock v1.3.1 + github.com/golang/mock v1.4.3 + github.com/golang/protobuf v1.4.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/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/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.8.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/tidwall/pretty v1.0.0 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect github.com/xdg/stringprep v1.0.0 // indirect - go.mongodb.org/mongo-driver v1.0.4 - golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 - golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 - 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 3249014..0eb690a 100644 --- a/go.sum +++ b/go.sum @@ -2,7 +2,29 @@ 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= @@ -36,8 +58,12 @@ github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n 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= @@ -64,12 +90,16 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1 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= @@ -77,6 +107,9 @@ github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2 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= @@ -90,17 +123,42 @@ github.com/go-redis/redis v6.15.6+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8w 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= @@ -114,16 +172,30 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU 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= @@ -137,17 +209,28 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ 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= @@ -178,6 +261,7 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J 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= @@ -188,10 +272,18 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV 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= @@ -211,6 +303,8 @@ github.com/lusis/slack-test v0.0.0-20190426140909-c40012f20018 h1:MNApn+Z+fIT4NP 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= @@ -239,6 +333,7 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ 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= @@ -251,6 +346,8 @@ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6 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= @@ -277,12 +374,15 @@ github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIw 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= @@ -307,6 +407,7 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx 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= @@ -322,6 +423,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 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= @@ -355,22 +458,29 @@ github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaU 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= @@ -384,22 +494,47 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf 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= @@ -412,26 +547,42 @@ golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73r 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= @@ -442,41 +593,87 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h 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= @@ -485,13 +682,26 @@ google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMt 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= @@ -499,19 +709,46 @@ google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRn 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= @@ -542,5 +779,9 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh 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= From b918f89f3959159b47dd02e336424afd097b46b7 Mon Sep 17 00:00:00 2001 From: mcgurn Date: Sat, 2 May 2020 12:36:56 -0600 Subject: [PATCH 11/23] added testing to preferences --- migrations/mysql/2_initial_userPref.sql | 6 + migrations/postgres/2_initial_userPref.sql | 4 + notification/NotifierWebPush.go | 8 +- preferences/{Option.go => option.go} | 15 +- preferences/option_test.go | 175 +++++++ .../{Preferences.go => preferences.go} | 0 preferences/{Repo.go => repo.go} | 3 - preferences/{RepoSql.go => repoSql.go} | 69 +-- preferences/repoSql_test.go | 260 ++++++++++ preferences/{Setting.go => setting.go} | 9 +- preferences/setting_test.go | 488 ++++++++++++++++++ 11 files changed, 965 insertions(+), 72 deletions(-) create mode 100644 migrations/mysql/2_initial_userPref.sql create mode 100644 migrations/postgres/2_initial_userPref.sql rename preferences/{Option.go => option.go} (87%) create mode 100644 preferences/option_test.go rename preferences/{Preferences.go => preferences.go} (100%) rename preferences/{Repo.go => repo.go} (94%) rename preferences/{RepoSql.go => repoSql.go} (63%) create mode 100644 preferences/repoSql_test.go rename preferences/{Setting.go => setting.go} (94%) create mode 100644 preferences/setting_test.go 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/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/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/preferences/Option.go b/preferences/option.go similarity index 87% rename from preferences/Option.go rename to preferences/option.go index b39105b..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{} @@ -29,13 +28,17 @@ func LoadOptionsGroup(jsonFile string) *OptionGroup { if err == nil { //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 94% rename from preferences/Repo.go rename to preferences/repo.go index 523d80f..b5d6c7b 100644 --- a/preferences/Repo.go +++ b/preferences/repo.go @@ -9,9 +9,6 @@ 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 dfa07f0..8b621ba 100644 --- a/preferences/RepoSql.go +++ b/preferences/repoSql.go @@ -5,13 +5,12 @@ package preferences import ( "database/sql" + "github.com/reaction-eng/restlib/users" - "log" ) -/** -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) + } +} From e8b170419a409f7f12dec2ae7299ae433458a033 Mon Sep 17 00:00:00 2001 From: mcgurn Date: Sun, 3 May 2020 17:09:15 -0600 Subject: [PATCH 12/23] testing for user helper --- mocks/mock_user.go | 38 + mocks/mock_user_helper.go | 409 +++++++ mocks/mock_users_repo.go | 12 - roles/{Documentation.go => documentation.go} | 0 roles/{Handlers.go => handlers.go} | 16 +- users/Facebook.go | 6 +- users/Google.go | 7 +- users/Repo.go | 8 - users/User.go | 5 +- users/User_test.go | 6 - users/{Helper.go => basicHelper.go} | 72 +- users/{BasicUser.go => basicUser.go} | 31 +- users/{Handlers.go => handlers.go} | 120 +- users/handlers_test.go | 1152 ++++++++++++++++++ users/helper.go | 20 + 15 files changed, 1747 insertions(+), 155 deletions(-) create mode 100644 mocks/mock_user_helper.go rename roles/{Documentation.go => documentation.go} (100%) rename roles/{Handlers.go => handlers.go} (93%) rename users/{Helper.go => basicHelper.go} (67%) rename users/{BasicUser.go => basicUser.go} (70%) rename users/{Handlers.go => handlers.go} (83%) create mode 100644 users/handlers_test.go create mode 100644 users/helper.go diff --git a/mocks/mock_user.go b/mocks/mock_user.go index 2d47f6d..3a5c49c 100644 --- a/mocks/mock_user.go +++ b/mocks/mock_user.go @@ -46,6 +46,18 @@ func (mr *MockUserMockRecorder) Activated() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Activated", reflect.TypeOf((*MockUser)(nil).Activated)) } +// AddOrganization mocks base method +func (m *MockUser) AddOrganization(arg0 int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "AddOrganization", arg0) +} + +// AddOrganization indicates an expected call of AddOrganization +func (mr *MockUserMockRecorder) AddOrganization(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddOrganization", reflect.TypeOf((*MockUser)(nil).AddOrganization), arg0) +} + // Email mocks base method func (m *MockUser) Email() string { m.ctrl.T.Helper() @@ -74,6 +86,20 @@ func (mr *MockUserMockRecorder) Id() *gomock.Call { 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() @@ -102,6 +128,18 @@ func (mr *MockUserMockRecorder) PasswordLogin() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PasswordLogin", reflect.TypeOf((*MockUser)(nil).PasswordLogin)) } +// RemoveOrganization mocks base method +func (m *MockUser) RemoveOrganization(arg0 int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RemoveOrganization", arg0) +} + +// RemoveOrganization indicates an expected call of RemoveOrganization +func (mr *MockUserMockRecorder) RemoveOrganization(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveOrganization", reflect.TypeOf((*MockUser)(nil).RemoveOrganization), arg0) +} + // SetEmail mocks base method func (m *MockUser) SetEmail(arg0 string) { m.ctrl.T.Helper() diff --git a/mocks/mock_user_helper.go b/mocks/mock_user_helper.go new file mode 100644 index 0000000..2805666 --- /dev/null +++ b/mocks/mock_user_helper.go @@ -0,0 +1,409 @@ +// 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) +} + +// 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) +} + +// 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 int, arg1 string) string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateJWTToken", arg0, arg1) + ret0, _ := ret[0].(string) + return ret0 +} + +// CreateJWTToken indicates an expected call of CreateJWTToken +func (mr *MockUserHelperMockRecorder) CreateJWTToken(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateJWTToken", reflect.TypeOf((*MockUserHelper)(nil).CreateJWTToken), arg0, arg1) +} + +// 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) +} + +// 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) +} + +// ListAllActiveUsers mocks base method +func (m *MockUserHelper) ListAllActiveUsers() ([]int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListAllActiveUsers") + ret0, _ := ret[0].([]int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListAllActiveUsers indicates an expected call of ListAllActiveUsers +func (mr *MockUserHelperMockRecorder) ListAllActiveUsers() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAllActiveUsers", reflect.TypeOf((*MockUserHelper)(nil).ListAllActiveUsers)) +} + +// ListAllUsers mocks base method +func (m *MockUserHelper) ListAllUsers() ([]int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListAllUsers") + ret0, _ := ret[0].([]int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListAllUsers indicates an expected call of ListAllUsers +func (mr *MockUserHelperMockRecorder) ListAllUsers() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAllUsers", reflect.TypeOf((*MockUserHelper)(nil).ListAllUsers)) +} + +// 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) +} + +// 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, string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidateToken", arg0) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(string) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// 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) +} + +// ValidateUser mocks base method +func (m *MockUserHelper) ValidateUser(arg0 users.User) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidateUser", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ValidateUser indicates an expected call of ValidateUser +func (mr *MockUserHelperMockRecorder) ValidateUser(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateUser", reflect.TypeOf((*MockUserHelper)(nil).ValidateUser), arg0) +} diff --git a/mocks/mock_users_repo.go b/mocks/mock_users_repo.go index c998b50..9c89ae6 100644 --- a/mocks/mock_users_repo.go +++ b/mocks/mock_users_repo.go @@ -62,18 +62,6 @@ func (mr *MockUserRepoMockRecorder) AddUser(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUser", reflect.TypeOf((*MockUserRepo)(nil).AddUser), arg0) } -// CleanUp mocks base method -func (m *MockUserRepo) CleanUp() { - m.ctrl.T.Helper() - m.ctrl.Call(m, "CleanUp") -} - -// CleanUp indicates an expected call of CleanUp -func (mr *MockUserRepoMockRecorder) CleanUp() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanUp", reflect.TypeOf((*MockUserRepo)(nil).CleanUp)) -} - // GetUser mocks base method func (m *MockUserRepo) GetUser(arg0 int) (users.User, error) { m.ctrl.T.Helper() 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 93% rename from roles/Handlers.go rename to roles/handlers.go index 295f468..5e34439 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,9 +51,6 @@ 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 @@ -85,5 +74,4 @@ func (handler *Handler) handleUserPermissionsGet(w http.ResponseWriter, r *http. } else { utils.ReturnJsonStatus(w, http.StatusUnsupportedMediaType, false, err.Error()) } - } diff --git a/users/Facebook.go b/users/Facebook.go index c7a7b46..cddc094 100644 --- a/users/Facebook.go +++ b/users/Facebook.go @@ -60,7 +60,7 @@ type facebookMeResponse struct { */ type FacebookHandler struct { // The user handler needs to have access to user repo - helper *Helper + helper Helper //Store the facebook info clientId string @@ -70,7 +70,7 @@ type FacebookHandler struct { /** * This struct is used */ -func NewFacebookHandler(helper *Helper, configuration configuration.Configuration) *FacebookHandler { +func NewFacebookHandler(helper Helper, configuration configuration.Configuration) *FacebookHandler { //Create a new facebook := &FacebookHandler{ helper: helper, @@ -244,7 +244,7 @@ func (fbHandler *FacebookHandler) handleUserLoginFacebook(w http.ResponseWriter, } //Create JWT token and Store the token in the response - user.SetToken(fbHandler.helper.passwordHelper.CreateJWTToken(user.Id(), user.Email())) + user.SetToken(fbHandler.helper.CreateJWTToken(user.Id(), user.Email())) //Check to see if the user was created if err == nil { diff --git a/users/Google.go b/users/Google.go index b0e7771..c8537e5 100644 --- a/users/Google.go +++ b/users/Google.go @@ -31,7 +31,7 @@ type GoogleLoginToken struct { */ type GoogleHandler struct { // The user handler needs to have access to user repo - helper *Helper + helper Helper //We need the oauth config oAuthConfig *oauth2.Config @@ -40,7 +40,7 @@ type GoogleHandler struct { /** * This struct is used */ -func NewGoogleHandler(helper *Helper, configuration configuration.Configuration) *GoogleHandler { +func NewGoogleHandler(helper Helper, configuration configuration.Configuration) *GoogleHandler { //Create a new google := &GoogleHandler{ helper: helper, @@ -156,7 +156,7 @@ func (gHandler *GoogleHandler) handleUserLoginGoogle(w http.ResponseWriter, r *h } //Create JWT token and Store the token in the response - user.SetToken(gHandler.helper.passwordHelper.CreateJWTToken(user.Id(), user.Email())) + user.SetToken(gHandler.helper.CreateJWTToken(user.Id(), user.Email())) //Check to see if the user was created if err == nil { @@ -164,5 +164,4 @@ func (gHandler *GoogleHandler) handleUserLoginGoogle(w http.ResponseWriter, r *h } else { utils.ReturnJsonError(w, http.StatusForbidden, err) } - } diff --git a/users/Repo.go b/users/Repo.go index b954aac..0b9b617 100644 --- a/users/Repo.go +++ b/users/Repo.go @@ -5,9 +5,6 @@ package users //go:generate mockgen -destination=../mocks/mock_users_repo.go -package=mocks -mock_names Repo=MockUserRepo github.com/reaction-eng/restlib/users Repo -/** -Define an interface that all Calc Repos must follow -*/ type Repo interface { /** Get the user with the email. An error is thrown is not found @@ -34,11 +31,6 @@ type Repo interface { */ ActivateUser(user User) error - /** - Allow databases to be closed - */ - CleanUp() - /** Create empty user */ diff --git a/users/User.go b/users/User.go index d55f6f3..4cdb824 100644 --- a/users/User.go +++ b/users/User.go @@ -19,10 +19,13 @@ type User interface { Password() string SetPassword(password string) - //Return the user email Token() string SetToken(token string) + AddOrganization(org int) + RemoveOrganization(org int) + Organizations() []int + //Check if the user was activated Activated() bool diff --git a/users/User_test.go b/users/User_test.go index b65eaf9..c4a8e76 100644 --- a/users/User_test.go +++ b/users/User_test.go @@ -16,16 +16,10 @@ import ( "github.com/reaction-eng/restlib/users" ) -/** -Function to carray along the -*/ type routingEnv struct { router *routing.MuxRouter } -/** -Perform the testing -*/ func TestUserRoutes(t *testing.T) { //Define the list of routes we testing diff --git a/users/Helper.go b/users/basicHelper.go similarity index 67% rename from users/Helper.go rename to users/basicHelper.go index 25687ae..992ab4c 100644 --- a/users/Helper.go +++ b/users/basicHelper.go @@ -5,12 +5,12 @@ package users import ( "errors" - "github.com/reaction-eng/restlib/passwords" "strings" -) -type Helper struct { + "github.com/reaction-eng/restlib/passwords" +) +type BasicHelper struct { //Hold the user repo Repo @@ -18,31 +18,29 @@ type Helper struct { passwords.ResetRepo //And store a password helper - passwordHelper passwords.Helper + passwords.Helper } -func NewUserHelper(usersRepo Repo, passRepo passwords.ResetRepo, passwordHelper passwords.Helper) *Helper { - - return &Helper{ - Repo: usersRepo, - ResetRepo: passRepo, - passwordHelper: passwordHelper, +func NewUserHelper(usersRepo Repo, passRepo passwords.ResetRepo, passwordHelper passwords.Helper) *BasicHelper { + return &BasicHelper{ + Repo: usersRepo, + ResetRepo: passRepo, + Helper: passwordHelper, } - } /** Static method to create a new user */ -func (helper *Helper) createUser(user User) error { +func (helper *BasicHelper) CreateUser(user User) error { //Make sure the info being passed in is valid - if ok, err := helper.validateUser(user); !ok { + if ok, err := helper.ValidateUser(user); !ok { return err } //Now hash the password - user.SetPassword(helper.passwordHelper.HashPassword(user.Password())) + user.SetPassword(helper.Helper.HashPassword(user.Password())) //Now store it newUser, err := helper.AddUser(user) @@ -53,7 +51,7 @@ func (helper *Helper) createUser(user User) error { } //Else issue the request - err = helper.IssueActivationRequest(helper.passwordHelper.TokenGenerator(), newUser.Id(), newUser.Email()) + err = helper.IssueActivationRequest(helper.Helper.TokenGenerator(), newUser.Id(), newUser.Email()) if err != nil { return err @@ -64,16 +62,17 @@ func (helper *Helper) createUser(user User) error { } /** -Validate incoming user details to make sure it has an email address and stuff +Validate incoming user details to make sure it has an email address and stuff, +//TODO: add organization check */ -func (helper *Helper) validateUser(user User) (bool, error) { +func (helper *BasicHelper) ValidateUser(user User) (bool, error) { if !strings.Contains(user.Email(), "@") { return false, errors.New("validate_missing_email") } //Check the password - err := helper.passwordHelper.ValidatePassword(user.Password()) + err := helper.Helper.ValidatePassword(user.Password()) //If the user already exists if err != nil { @@ -95,7 +94,7 @@ func (helper *Helper) validateUser(user User) (bool, error) { /** Updates everything from the password */ -func (helper *Helper) updateUser(userId int, newUser User) (User, error) { +func (helper *BasicHelper) Update(userId int, newUser User) (User, error) { //Load up the user oldUser, err := helper.GetUser(userId) @@ -129,22 +128,13 @@ func (helper *Helper) updateUser(userId int, newUser User) (User, error) { } -/** -Define a struct for just updating password -*/ -type updatePasswordChangeStruct struct { - Email string `json:"email"` - Password string `json:"password"` - PasswordOld string `json:"passwordold"` -} - /** Updates everything from the password */ -func (helper *Helper) passwordChange(userId int, passwordChange updatePasswordChangeStruct) error { +func (helper *BasicHelper) PasswordChange(userId int, email string, newPassword string, oldPassword string) error { //Clean up the email - passwordChange.Email = strings.TrimSpace(strings.ToLower(passwordChange.Email)) + email = strings.TrimSpace(strings.ToLower(email)) //Load up the user oldUser, err := helper.GetUser(userId) @@ -155,12 +145,12 @@ func (helper *Helper) passwordChange(userId int, passwordChange updatePasswordCh } //Make sure that the emails match - if passwordChange.Email != oldUser.Email() { + if email != oldUser.Email() { return errors.New("password_change_forbidden") } //Make sure the old password matches - passwordsMath := helper.passwordHelper.ComparePasswords(oldUser.Password(), passwordChange.PasswordOld) + passwordsMath := helper.Helper.ComparePasswords(oldUser.Password(), oldPassword) //Make sure that the emails match if !passwordsMath { @@ -168,7 +158,7 @@ func (helper *Helper) passwordChange(userId int, passwordChange updatePasswordCh } //Make sure the new password is valid - err = helper.passwordHelper.ValidatePassword(passwordChange.Password) + err = helper.Helper.ValidatePassword(newPassword) //If the password is bad if err != nil { @@ -176,7 +166,7 @@ func (helper *Helper) passwordChange(userId int, passwordChange updatePasswordCh } //So it looks like we can update it, so hash the new password - oldUser.SetPassword(helper.passwordHelper.HashPassword(passwordChange.Password)) + oldUser.SetPassword(helper.Helper.HashPassword(newPassword)) //Now update in the repo _, err = helper.UpdateUser(oldUser) @@ -188,7 +178,7 @@ func (helper *Helper) passwordChange(userId int, passwordChange updatePasswordCh /** Updates everything from the password */ -func (helper *Helper) passwordChangeForced(userId int, email string, newPassword string) error { +func (helper *BasicHelper) PasswordChangeForced(userId int, email string, newPassword string) error { //Clean up the email email = strings.TrimSpace(strings.ToLower(email)) @@ -202,7 +192,7 @@ func (helper *Helper) passwordChangeForced(userId int, email string, newPassword //} //Make sure the new password is valid - err = helper.passwordHelper.ValidatePassword(newPassword) + err = helper.Helper.ValidatePassword(newPassword) //If the password is bad if err != nil { @@ -210,7 +200,7 @@ func (helper *Helper) passwordChangeForced(userId int, email string, newPassword } //So it looks like we can update it, so hash the new password - oldUser.SetPassword(helper.passwordHelper.HashPassword(newPassword)) + oldUser.SetPassword(helper.Helper.HashPassword(newPassword)) //Now update in the repo _, err = helper.UpdateUser(oldUser) @@ -222,7 +212,7 @@ func (helper *Helper) passwordChangeForced(userId int, email string, newPassword /** Login in the user */ -func (helper *Helper) login(userPassword string, user User) (User, error) { +func (helper *BasicHelper) Login(userPassword string, orgId int, user User) (User, error) { //Make sure the user can login with password if !user.PasswordLogin() { @@ -235,7 +225,7 @@ func (helper *Helper) login(userPassword string, user User) (User, error) { } //Make sure the new password is valid - err := helper.passwordHelper.ValidatePassword(userPassword) + err := helper.Helper.ValidatePassword(userPassword) //If the password is bad if err != nil { @@ -243,7 +233,7 @@ func (helper *Helper) login(userPassword string, user User) (User, error) { } //Now see if we login - passwordsMath := helper.passwordHelper.ComparePasswords(user.Password(), userPassword) + passwordsMath := helper.Helper.ComparePasswords(user.Password(), userPassword) //Blank out the password before returning user.SetPassword("") @@ -254,7 +244,7 @@ func (helper *Helper) login(userPassword string, user User) (User, error) { } //Create JWT token and Store the token in the response - user.SetToken(helper.passwordHelper.CreateJWTToken(user.Id(), user.Email())) + user.SetToken(helper.Helper.CreateJWTToken(user.Id(), user.Email())) return user, nil } diff --git a/users/BasicUser.go b/users/basicUser.go similarity index 70% rename from users/BasicUser.go rename to users/basicUser.go index 9f82e92..bcdcd15 100644 --- a/users/BasicUser.go +++ b/users/basicUser.go @@ -5,9 +5,9 @@ package users import "strings" -//a struct to rep user account type BasicUser struct { Id_ int `json:"id"` + Organizations_ []int `json:"organizations"` Email_ string `json:"email"` password_ string `json:"-"` Token_ string `json:"token";sql:"-"` @@ -55,6 +55,33 @@ func (basic *BasicUser) PasswordLogin() bool { return basic.passwordlogin_ } +func (basic *BasicUser) AddOrganization(org int) { + if basic.Organizations_ == nil { + basic.Organizations_ = make([]int, 0) + } + basic.Organizations_ = append(basic.Organizations_, org) +} + +func (basic *BasicUser) RemoveOrganization(org int) { + if basic.Organizations_ == nil { + return + } + + updatedList := make([]int, 0) + for _, orgIdOld := range basic.Organizations_ { + if orgIdOld != org { + updatedList = append(updatedList, orgIdOld) + } + } + + basic.Organizations_ = updatedList + +} + +func (basic *BasicUser) Organizations() []int { + return basic.Organizations_ +} + /** Provide code to copy the user into this user */ @@ -68,5 +95,5 @@ func (basic *BasicUser) CopyFrom(from User) { basic.Token_ = from.Token() basic.activated_ = from.Activated() basic.passwordlogin_ = from.PasswordLogin() - + basic.Organizations_ = from.Organizations() } diff --git a/users/Handlers.go b/users/handlers.go similarity index 83% rename from users/Handlers.go rename to users/handlers.go index 2219873..a774671 100644 --- a/users/Handlers.go +++ b/users/handlers.go @@ -5,6 +5,7 @@ package users import ( "encoding/json" + "errors" "net/http" "strings" @@ -12,13 +13,9 @@ import ( "github.com/reaction-eng/restlib/utils" ) -/** - * This struct is used - */ type Handler struct { - //Store the user helper - userHelper *Helper + userHelper Helper //Keep track if we want to allow userCreation allowUserCreation bool @@ -27,7 +24,7 @@ type Handler struct { /** * This struct is used */ -func NewHandler(userHelper *Helper, allowUserCreation bool) *Handler { +func NewHandler(userHelper Helper, allowUserCreation bool) *Handler { //Build a new User Handler handler := Handler{ userHelper: userHelper, @@ -146,12 +143,10 @@ func (handler *Handler) handleUserCreate(w http.ResponseWriter, r *http.Request) //Create an empty new user newUser := handler.userHelper.NewEmptyUser() - /** - Define a struct for just updating password - */ type newUserStruct struct { - Email string `json:"email"` - Password string `json:"password"` + Email string `json:"email"` + Password string `json:"password"` + OrganizationId int `json:"organizationId` } //Create the new user @@ -162,41 +157,31 @@ func (handler *Handler) handleUserCreate(w http.ResponseWriter, r *http.Request) if err != nil { utils.ReturnJsonStatus(w, http.StatusUnprocessableEntity, false, err.Error()) return - } //Copy over the new user data newUser.SetEmail(newUserInfo.Email) newUser.SetPassword(newUserInfo.Password) + newUser.AddOrganization(newUserInfo.OrganizationId) - //Now create the new suer - err = handler.userHelper.createUser(newUser) + //Now create the new user + err = handler.userHelper.CreateUser(newUser) if err != nil { utils.ReturnJsonStatus(w, http.StatusUnprocessableEntity, false, err.Error()) return } - - //Check to see if the user was created - if err == nil { - utils.ReturnJsonStatus(w, http.StatusCreated, true, "create_user_added") - } else { - utils.ReturnJsonStatus(w, http.StatusUnprocessableEntity, false, err.Error()) - } - + utils.ReturnJsonStatus(w, http.StatusCreated, true, "create_user_added") } /** Function used to create new user */ func (handler *Handler) handleUserLogin(w http.ResponseWriter, r *http.Request) { - - /** - Define a struct for just updating password - */ type loginUserStruct struct { - Email string `json:"email"` - Password string `json:"password"` + Email string `json:"email"` + Password string `json:"password"` + OrganizationId int `json:"organizationId"` } userCred := &loginUserStruct{} @@ -209,7 +194,6 @@ func (handler *Handler) handleUserLogin(w http.ResponseWriter, r *http.Request) } - //Now look up the user user, err := handler.userHelper.GetUserByEmail(strings.TrimSpace(strings.ToLower(userCred.Email))) //check for an error @@ -220,7 +204,7 @@ func (handler *Handler) handleUserLogin(w http.ResponseWriter, r *http.Request) } //We have the user, try to login - user, err = handler.userHelper.login(userCred.Password, user) + user, err = handler.userHelper.Login(userCred.Password, userCred.OrganizationId, user) //If there is an error, don't login if err != nil { @@ -229,29 +213,29 @@ func (handler *Handler) handleUserLogin(w http.ResponseWriter, r *http.Request) return } - //Check to see if the user was created - if err == nil { - utils.ReturnJson(w, http.StatusCreated, user) - } else { - utils.ReturnJsonError(w, http.StatusForbidden, err) - } - + utils.ReturnJson(w, http.StatusCreated, user) } /** -Updates the password for this user +updates everything but the password for the user */ func (handler *Handler) handleUserUpdate(w http.ResponseWriter, r *http.Request) { + 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 + } + //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 := loggedInUserString.(int) //Grab the id of the user that send the request //Now load the current user from the repo user, err := handler.userHelper.GetUser(loggedInUser) //Check for an error if err != nil { - utils.ReturnJsonError(w, http.StatusUnprocessableEntity, err) + utils.ReturnJsonError(w, http.StatusForbidden, err) return } @@ -264,13 +248,13 @@ func (handler *Handler) handleUserUpdate(w http.ResponseWriter, r *http.Request) } //Now update the user - user, err = handler.userHelper.updateUser(loggedInUser, user) + user, err = handler.userHelper.Update(loggedInUser, user) //Check to see if the user was created if err == nil { utils.ReturnJson(w, http.StatusAccepted, user) } else { - utils.ReturnJsonError(w, http.StatusForbidden, err) + utils.ReturnJsonError(w, http.StatusUnprocessableEntity, err) } } @@ -280,18 +264,22 @@ Get the current up to date user */ func (handler *Handler) handleUserGet(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 + 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 := loggedInUserString.(int) //Grab the id of the user that send the request //Get the user user, err := handler.userHelper.GetUser(loggedInUser) - //Make sure we null the password - //Blank out the password before returning - user.SetPassword("") - //Check to see if the user was created if err == nil { + //Make sure we null the password + //Blank out the password before returning + user.SetPassword("") + utils.ReturnJson(w, http.StatusOK, user) } else { utils.ReturnJsonStatus(w, http.StatusUnsupportedMediaType, false, err.Error()) @@ -303,11 +291,19 @@ func (handler *Handler) handleUserGet(w http.ResponseWriter, r *http.Request) { Updates the password for this user */ func (handler *Handler) handlePasswordUpdate(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 + 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 := loggedInUserString.(int) //Grab the id of the user that send the request //Create a new password change object + type updatePasswordChangeStruct struct { + Email string `json:"email"` + Password string `json:"password"` + PasswordOld string `json:"passwordold"` + } info := updatePasswordChangeStruct{} //Now get the json info @@ -319,7 +315,7 @@ func (handler *Handler) handlePasswordUpdate(w http.ResponseWriter, r *http.Requ } //Now update the password - err = handler.userHelper.passwordChange(loggedInUser, info) + err = handler.userHelper.PasswordChange(loggedInUser, info.Email, info.Password, info.PasswordOld) //Check to see if the user was created if err == nil { @@ -341,6 +337,7 @@ func (handler *Handler) handlePasswordResetGet(w http.ResponseWriter, r *http.Re //Only take the first one if !ok || len(keys[0]) < 1 { utils.ReturnJsonStatus(w, http.StatusUnprocessableEntity, false, "password_change_missing_email") + return } //Get the email @@ -356,11 +353,11 @@ func (handler *Handler) handlePasswordResetGet(w http.ResponseWriter, r *http.Re } //Now issue a request - err = handler.userHelper.IssueResetRequest(handler.userHelper.passwordHelper.TokenGenerator(), user.Id(), user.Email()) + err = handler.userHelper.IssueResetRequest(handler.userHelper.TokenGenerator(), user.Id(), user.Email()) //There was a real error return if err != nil { - utils.ReturnJsonError(w, http.StatusNotFound, err) + utils.ReturnJsonError(w, http.StatusServiceUnavailable, err) return } @@ -369,9 +366,6 @@ func (handler *Handler) handlePasswordResetGet(w http.ResponseWriter, r *http.Re } -/** -Function to request a password change -*/ func (handler *Handler) handlePasswordResetPut(w http.ResponseWriter, r *http.Request) { //Define a local struct to get the email out of the request @@ -411,7 +405,7 @@ func (handler *Handler) handlePasswordResetPut(w http.ResponseWriter, r *http.Re } //Now update the password - err = handler.userHelper.passwordChangeForced(user.Id(), user.Email(), info.Password) + err = handler.userHelper.PasswordChangeForced(user.Id(), user.Email(), info.Password) //Return the error if err != nil { utils.ReturnJsonError(w, http.StatusForbidden, err) @@ -428,9 +422,6 @@ func (handler *Handler) handlePasswordResetPut(w http.ResponseWriter, r *http.Re } } -/** -Function to request a password change -*/ func (handler *Handler) handleUserActivationPut(w http.ResponseWriter, r *http.Request) { //Define a local struct to get the email out of the request @@ -497,6 +488,7 @@ func (handler *Handler) handleUserActivationGet(w http.ResponseWriter, r *http.R //Only take the first one if !ok || len(keys[0]) < 1 { utils.ReturnJsonStatus(w, http.StatusUnprocessableEntity, false, "activation_token_missing_email") + return } //Get the email @@ -515,13 +507,13 @@ func (handler *Handler) handleUserActivationGet(w http.ResponseWriter, r *http.R //If the user is not already active if user.Activated() { utils.ReturnJsonStatus(w, http.StatusOK, true, "activation_token_request_received") + return } //Else issue the request - err = handler.userHelper.IssueActivationRequest(handler.userHelper.passwordHelper.TokenGenerator(), user.Id(), user.Email()) + err = handler.userHelper.IssueActivationRequest(handler.userHelper.TokenGenerator(), user.Id(), user.Email()) - //There was a real error return if err != nil { - utils.ReturnJsonError(w, http.StatusNotFound, err) + utils.ReturnJsonError(w, http.StatusServiceUnavailable, err) return } diff --git a/users/handlers_test.go b/users/handlers_test.go new file mode 100644 index 0000000..586a365 --- /dev/null +++ b/users/handlers_test.go @@ -0,0 +1,1152 @@ +// 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 ( + "errors" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/golang/mock/gomock" + "github.com/reaction-eng/restlib/mocks" + "github.com/reaction-eng/restlib/users" + "github.com/stretchr/testify/assert" +) + +func TestNewHandler(t *testing.T) { + // arrange + mockCtrl := gomock.NewController(t) + mockUserHelper := mocks.NewMockUserHelper(mockCtrl) + + // act + handler := users.NewHandler(mockUserHelper, true) + + // assert + assert.NotNil(t, handler) +} + +func TestHandler_handleUserCreate(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + comment string + body io.Reader + expectedUser func() users.User + expectDecodeSuccessful bool + createUserError error + expectedStatus int + expectedResponse string + }{ + { + comment: "working", + body: strings.NewReader(`{"email":"user@example.info", "password":"new password", "organizationId": 34 }`), + expectedUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().SetEmail("user@example.info").Times(1) + user.EXPECT().SetPassword("new password").Times(1) + user.EXPECT().AddOrganization(34).Times(1) + return user + }, + expectDecodeSuccessful: true, + createUserError: nil, + expectedStatus: http.StatusCreated, + expectedResponse: "{\"message\":\"create_user_added\",\"status\":true}\n", + }, + { + comment: "bad json", + body: strings.NewReader(`{{"email":"user@example.info", "password":"new password", "organizationId": 34 }`), + expectedUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + return user + }, + expectDecodeSuccessful: false, + createUserError: nil, + expectedStatus: http.StatusUnprocessableEntity, + expectedResponse: "{\"message\":\"invalid character '{' looking for beginning of object key string\",\"status\":false}\n", + }, + { + comment: "can't add user", + body: strings.NewReader(`{"email":"user@example.info", "password":"new password", "organizationId": 34 }`), + expectedUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().SetEmail("user@example.info").Times(1) + user.EXPECT().SetPassword("new password").Times(1) + user.EXPECT().AddOrganization(34).Times(1) + return user + }, + expectDecodeSuccessful: true, + createUserError: errors.New("can't add user"), + expectedStatus: http.StatusUnprocessableEntity, + expectedResponse: "{\"message\":\"can't add user\",\"status\":false}\n", + }, + } + + // arrange + for _, testCase := range testCases { + mockHelper := mocks.NewMockUserHelper(mockCtrl) + + mockUser := testCase.expectedUser() + mockHelper.EXPECT().NewEmptyUser().Times(1).Return(mockUser) + + if testCase.expectDecodeSuccessful { + mockHelper.EXPECT().CreateUser(mockUser).Times(1).Return(testCase.createUserError) + } + + handler := users.NewHandler(mockHelper, true) + router := mocks.NewTestRouter(handler, nil) // not logged in + + req := httptest.NewRequest("POST", "http://localhost/users/new", 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()) + } +} + +func TestHandler_handleUserLogin(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + comment string + body io.Reader + expectedUser func() users.User + expectedPassword string + expectedOrgId int + expectedGetUserByEmailCount int + getUserByEmailError error + loginCount int + loginError error + expectedStatus int + expectedResponse string + }{ + { + comment: "working", + body: strings.NewReader(`{"email":"user@example.info", "password":"new password", "organizationId": 34 }`), + expectedUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + return user + }, + getUserByEmailError: nil, + expectedGetUserByEmailCount: 1, + loginError: nil, + loginCount: 1, + expectedStatus: http.StatusCreated, + expectedResponse: "{}\n", // empty user return + }, + { + comment: "should clean up the email name", + body: strings.NewReader(`{"email":" uSeR@example.info ", "password":"new password", "organizationId": 34 }`), + expectedUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + return user + }, + getUserByEmailError: nil, + expectedGetUserByEmailCount: 1, + loginError: nil, + loginCount: 1, + expectedStatus: http.StatusCreated, + expectedResponse: "{}\n", // empty user return + }, + { + comment: "can't find user error", + body: strings.NewReader(`{"email":" uSeR@example.info ", "password":"new password", "organizationId": 34 }`), + expectedUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + return user + }, + getUserByEmailError: errors.New("can't find user"), + expectedGetUserByEmailCount: 1, + loginError: nil, + loginCount: 0, + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"can't find user\",\"status\":false}\n", + }, + { + comment: "can't login error", + body: strings.NewReader(`{"email":" uSeR@example.info ", "password":"new password", "organizationId": 34 }`), + expectedUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + return user + }, + getUserByEmailError: nil, + expectedGetUserByEmailCount: 1, + loginError: errors.New("wrong password"), + loginCount: 1, + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"wrong password\",\"status\":false}\n", + }, + { + comment: "can't decode error", + body: strings.NewReader(`{{}`), + expectedUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + return user + }, + getUserByEmailError: nil, + expectedGetUserByEmailCount: 0, + loginError: nil, + loginCount: 0, + expectedStatus: http.StatusUnprocessableEntity, + expectedResponse: "{\"message\":\"invalid character '{' looking for beginning of object key string\",\"status\":false}\n", + }, + } + + // arrange + for _, testCase := range testCases { + mockHelper := mocks.NewMockUserHelper(mockCtrl) + + mockUser := testCase.expectedUser() + + mockHelper.EXPECT().GetUserByEmail("user@example.info").Times(testCase.expectedGetUserByEmailCount).Return(mockUser, testCase.getUserByEmailError) + mockHelper.EXPECT().Login("new password", 34, mockUser).Times(testCase.loginCount).Return(mockUser, testCase.loginError) + + handler := users.NewHandler(mockHelper, true) + router := mocks.NewTestRouter(handler, nil) // not logged in + + req := httptest.NewRequest("POST", "http://localhost/users/login", 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()) + } +} + +func TestHandler_handleUserUpdate(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + type testUser struct { + users.BasicUser + OtherInfo string `json:"otherInfo"` + } + + loggedInUser := mocks.NewMockUser(mockCtrl) + loggedInUser.EXPECT().Id().MinTimes(0).Return(34) + + testCases := []struct { + comment string + body io.Reader + loggedInUser users.User + expectedUserAfterDecode func() users.User + getUserError error + getUserCount int + updateError error + updateCount int + expectedStatus int + expectedResponse string + }{ + { + comment: "nobody logged in", + body: strings.NewReader(`{"email":"user@example.info", "otherInfo":"new other info" }`), + loggedInUser: nil, + expectedUserAfterDecode: func() users.User { + user := &testUser{} + return user + }, + getUserError: nil, + getUserCount: 0, + updateError: nil, + updateCount: 0, + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"no_user_logged_in\",\"status\":false}\n", + }, + { + comment: "working", + body: strings.NewReader(`{"email":"user@example.info", "otherInfo":"new other info" }`), + loggedInUser: loggedInUser, + expectedUserAfterDecode: func() users.User { + user := &testUser{ + OtherInfo: "new other info", + BasicUser: users.BasicUser{ + Id_: 34, + Email_: "user@example.info", + }, + } + return user + }, + getUserError: nil, + getUserCount: 1, + updateError: nil, + updateCount: 1, + expectedStatus: http.StatusAccepted, + expectedResponse: "{\"id\":34,\"organizations\":null,\"email\":\"user@example.info\",\"token\":\"\",\"otherInfo\":\"new other info\"}\n", // empty user return + }, + { + comment: "can't find user", + body: strings.NewReader(`{"email":"user@example.info", "otherInfo":"new other info" }`), + loggedInUser: loggedInUser, + expectedUserAfterDecode: func() users.User { + user := &testUser{} + return user + }, + getUserError: errors.New("can't get user"), + getUserCount: 1, + updateError: nil, + updateCount: 0, + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"can't get user\",\"status\":false}\n", + }, + { + comment: "can't decode", + body: strings.NewReader(`{{}`), + loggedInUser: loggedInUser, + expectedUserAfterDecode: func() users.User { + user := &testUser{} + return user + }, + getUserError: nil, + getUserCount: 1, + updateError: nil, + updateCount: 0, + expectedStatus: http.StatusUnprocessableEntity, + expectedResponse: "{\"message\":\"invalid character '{' looking for beginning of object key string\",\"status\":false}\n", + }, + { + comment: "can't update user user", + body: strings.NewReader(`{"email":"user@example.info", "otherInfo":"new other info" }`), + loggedInUser: loggedInUser, + expectedUserAfterDecode: func() users.User { + user := &testUser{ + OtherInfo: "new other info", + BasicUser: users.BasicUser{ + Id_: 34, + Email_: "user@example.info", + }, + } + return user + }, + getUserError: nil, + getUserCount: 1, + updateError: errors.New("can't update user"), + updateCount: 1, + expectedStatus: http.StatusUnprocessableEntity, + expectedResponse: "{\"message\":\"can't update user\",\"status\":false}\n", + }, + } + + // arrange + for _, testCase := range testCases { + mockHelper := mocks.NewMockUserHelper(mockCtrl) + + user := &testUser{ + BasicUser: users.BasicUser{ + Id_: 34, + }, + } + + mockHelper.EXPECT().GetUser(34).Times(testCase.getUserCount).Return(user, testCase.getUserError) + mockHelper.EXPECT().Update(34, testCase.expectedUserAfterDecode()).Times(testCase.updateCount).Return(testCase.expectedUserAfterDecode(), testCase.updateError) + + handler := users.NewHandler(mockHelper, true) + router := mocks.NewTestRouter(handler, testCase.loggedInUser) // not logged in + + req := httptest.NewRequest("PUT", "http://localhost/users/", 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()) + } +} + +func TestHandler_handleUserGet(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + loggedInUser := mocks.NewMockUser(mockCtrl) + loggedInUser.EXPECT().Id().MinTimes(0).Return(34) + + testCases := []struct { + comment string + loggedInUser users.User + getUserUser func() users.User + getUserError error + getUserCount int + expectedStatus int + expectedResponse string + }{ + { + comment: "nobody logged in", + loggedInUser: nil, + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().SetPassword("").Times(0) + return user + }, + getUserError: nil, + getUserCount: 0, + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"no_user_logged_in\",\"status\":false}\n", + }, + { + comment: "everything working", + loggedInUser: loggedInUser, + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().SetPassword("").Times(1) + return user + }, + getUserError: nil, + getUserCount: 1, + expectedStatus: http.StatusOK, + expectedResponse: "{}\n", + }, + { + comment: "everything working", + loggedInUser: loggedInUser, + getUserUser: func() users.User { + return nil + }, + getUserError: errors.New("can't get user"), + getUserCount: 1, + expectedStatus: http.StatusUnsupportedMediaType, + expectedResponse: "{\"message\":\"can't get user\",\"status\":false}\n", + }, + } + + // arrange + for _, testCase := range testCases { + mockHelper := mocks.NewMockUserHelper(mockCtrl) + + mockHelper.EXPECT().GetUser(34).Times(testCase.getUserCount).Return(testCase.getUserUser(), testCase.getUserError) + + handler := users.NewHandler(mockHelper, true) + router := mocks.NewTestRouter(handler, testCase.loggedInUser) // not logged in + + req := httptest.NewRequest("GET", "http://localhost/users/", 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_handlePasswordUpdate(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + loggedInUser := mocks.NewMockUser(mockCtrl) + loggedInUser.EXPECT().Id().MinTimes(0).Return(34) + + testCases := []struct { + comment string + body io.Reader + loggedInUser users.User + passwordChangeCount int + passwordChangeError error + expectedStatus int + expectedResponse string + }{ + { + comment: "working", + body: strings.NewReader(`{"email":"user@example.info", "password":"new password", "passwordold": "old password" }`), + loggedInUser: loggedInUser, + passwordChangeCount: 1, + passwordChangeError: nil, + expectedStatus: http.StatusAccepted, + expectedResponse: "{\"message\":\"password_change_success\",\"status\":true}\n", + }, + { + comment: "nobody logged in", + body: strings.NewReader(`{"email":"user@example.info", "password":"new password", "passwordold": "old password" }`), + loggedInUser: nil, + passwordChangeCount: 0, + passwordChangeError: nil, + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"no_user_logged_in\",\"status\":false}\n", + }, + { + comment: "PasswordChangeError", + body: strings.NewReader(`{"email":"user@example.info", "password":"new password", "passwordold": "old password" }`), + loggedInUser: loggedInUser, + passwordChangeCount: 1, + passwordChangeError: errors.New("password change error"), + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"password change error\",\"status\":false}\n", + }, + { + comment: "Decode Error", + body: strings.NewReader(`{{}`), + loggedInUser: loggedInUser, + passwordChangeCount: 0, + passwordChangeError: nil, + expectedStatus: http.StatusUnprocessableEntity, + expectedResponse: "{\"message\":\"invalid character '{' looking for beginning of object key string\",\"status\":false}\n", + }, + } + + // arrange + for _, testCase := range testCases { + mockHelper := mocks.NewMockUserHelper(mockCtrl) + + mockHelper.EXPECT().PasswordChange(34, "user@example.info", "new password", "old password").Times(testCase.passwordChangeCount).Return(testCase.passwordChangeError) + + handler := users.NewHandler(mockHelper, true) + router := mocks.NewTestRouter(handler, testCase.loggedInUser) // not logged in + + req := httptest.NewRequest("POST", "http://localhost/users/password/change", 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()) + } +} + +func TestHandler_handlePasswordResetGet(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + comment string + url string + getUserUser func() users.User + getUserByEmailCount int + getUserByEmailError error + issueResetRequestCount int + issueResetRequestError error + tokenGeneratorCount int + expectedStatus int + expectedResponse string + }{ + { + comment: "working", + url: "http://localhost/users/password/reset?email=user@example.com", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(1).Return(34) + user.EXPECT().Email().Times(1).Return("user@example.com") + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: nil, + issueResetRequestCount: 1, + issueResetRequestError: nil, + tokenGeneratorCount: 1, + expectedStatus: http.StatusOK, + expectedResponse: "{\"message\":\"password_change_request_received\",\"status\":true}\n", + }, + { + comment: "no email specified", + url: "http://localhost/users/password/reset", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(0).Return(34) + user.EXPECT().Email().Times(0).Return("user@example.com") + return user + }, + getUserByEmailCount: 0, + getUserByEmailError: nil, + issueResetRequestCount: 0, + issueResetRequestError: nil, + tokenGeneratorCount: 0, + expectedStatus: http.StatusUnprocessableEntity, + expectedResponse: "{\"message\":\"password_change_missing_email\",\"status\":false}\n", + }, + { + comment: "empty email", + url: "http://localhost/users/password/reset?email=", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(0).Return(34) + user.EXPECT().Email().Times(0).Return("user@example.com") + return user + }, + getUserByEmailCount: 0, + getUserByEmailError: nil, + issueResetRequestCount: 0, + issueResetRequestError: nil, + tokenGeneratorCount: 0, + expectedStatus: http.StatusUnprocessableEntity, + expectedResponse: "{\"message\":\"password_change_missing_email\",\"status\":false}\n", + }, + { + comment: "can't find user", + url: "http://localhost/users/password/reset?email=user@example.com", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(0).Return(34) + user.EXPECT().Email().Times(0).Return("user@example.com") + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: errors.New("can't find user"), + issueResetRequestCount: 0, + issueResetRequestError: nil, + tokenGeneratorCount: 0, + expectedStatus: http.StatusOK, + expectedResponse: "{\"message\":\"password_change_request_received\",\"status\":true}\n", + }, + { + comment: "can't issue request", + url: "http://localhost/users/password/reset?email=user@example.com", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(1).Return(34) + user.EXPECT().Email().Times(1).Return("user@example.com") + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: nil, + issueResetRequestCount: 1, + issueResetRequestError: errors.New("can't issue request"), + tokenGeneratorCount: 1, + expectedStatus: http.StatusServiceUnavailable, + expectedResponse: "{\"message\":\"can't issue request\",\"status\":false}\n", + }, + } + + // arrange + for _, testCase := range testCases { + mockHelper := mocks.NewMockUserHelper(mockCtrl) + + mockHelper.EXPECT().GetUserByEmail("user@example.com").Times(testCase.getUserByEmailCount).Return(testCase.getUserUser(), testCase.getUserByEmailError) + token := "token123" + mockHelper.EXPECT().TokenGenerator().Times(testCase.tokenGeneratorCount).Return(token) + mockHelper.EXPECT().IssueResetRequest(token, 34, "user@example.com").Times(testCase.issueResetRequestCount).Return(testCase.issueResetRequestError) + + handler := users.NewHandler(mockHelper, true) + router := mocks.NewTestRouter(handler, nil) // 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_handlePasswordResetPut(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + comment string + body io.Reader + getUserUser func() users.User + getUserByEmailCount int + getUserByEmailError error + checkForResetTokenCount int + checkForResetTokenError error + passwordChangeForcedCount int + passwordChangeForcedError error + useTokenCount int + userTokenError error + expectedStatus int + expectedResponse string + }{ + { + comment: "working", + body: strings.NewReader(`{"email":"user@example.info", "reset_token":"reset 123", "password": "new password" }`), + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(2).Return(34) + user.EXPECT().Email().Times(1).Return("user@example.info") + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: nil, + checkForResetTokenCount: 1, + checkForResetTokenError: nil, + passwordChangeForcedCount: 1, + passwordChangeForcedError: nil, + useTokenCount: 1, + userTokenError: nil, + expectedStatus: http.StatusAccepted, + expectedResponse: "{\"message\":\"password_change_success\",\"status\":true}\n", + }, + { + comment: "bad decode", + body: strings.NewReader(`{{}`), + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(0).Return(34) + user.EXPECT().Email().Times(0).Return("user@example.info") + return user + }, + getUserByEmailCount: 0, + getUserByEmailError: nil, + checkForResetTokenCount: 0, + checkForResetTokenError: nil, + passwordChangeForcedCount: 0, + passwordChangeForcedError: nil, + useTokenCount: 0, + userTokenError: nil, + expectedStatus: http.StatusUnprocessableEntity, + expectedResponse: "{\"message\":\"invalid character '{' looking for beginning of object key string\",\"status\":false}\n", + }, + { + comment: "no user", + body: strings.NewReader(`{"email":"user@example.info", "reset_token":"reset 123", "password": "new password" }`), + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(0).Return(34) + user.EXPECT().Email().Times(0).Return("user@example.info") + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: errors.New("no user error"), + checkForResetTokenCount: 0, + checkForResetTokenError: nil, + passwordChangeForcedCount: 0, + passwordChangeForcedError: nil, + useTokenCount: 0, + userTokenError: nil, + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"password_change_forbidden\",\"status\":false}\n", + }, + { + comment: "no token error", + body: strings.NewReader(`{"email":"user@example.info", "reset_token":"reset 123", "password": "new password" }`), + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(1).Return(34) + user.EXPECT().Email().Times(0).Return("user@example.info") + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: nil, + checkForResetTokenCount: 1, + checkForResetTokenError: errors.New("no token error"), + passwordChangeForcedCount: 0, + passwordChangeForcedError: nil, + useTokenCount: 0, + userTokenError: nil, + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"password_change_forbidden\",\"status\":false}\n", + }, + { + comment: "can't change password", + body: strings.NewReader(`{"email":"user@example.info", "reset_token":"reset 123", "password": "new password" }`), + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(2).Return(34) + user.EXPECT().Email().Times(1).Return("user@example.info") + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: nil, + checkForResetTokenCount: 1, + checkForResetTokenError: nil, + passwordChangeForcedCount: 1, + passwordChangeForcedError: errors.New("can't change password"), + useTokenCount: 0, + userTokenError: nil, + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"can't change password\",\"status\":false}\n", + }, + { + comment: "user token error", + body: strings.NewReader(`{"email":"user@example.info", "reset_token":"reset 123", "password": "new password" }`), + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(2).Return(34) + user.EXPECT().Email().Times(1).Return("user@example.info") + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: nil, + checkForResetTokenCount: 1, + checkForResetTokenError: nil, + passwordChangeForcedCount: 1, + passwordChangeForcedError: nil, + useTokenCount: 1, + userTokenError: errors.New("user token error"), + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"user token error\",\"status\":false}\n", + }, + } + + // arrange + for _, testCase := range testCases { + mockHelper := mocks.NewMockUserHelper(mockCtrl) + + reqId := 454 + + mockHelper.EXPECT().GetUserByEmail("user@example.info").Times(testCase.getUserByEmailCount).Return(testCase.getUserUser(), testCase.getUserByEmailError) + mockHelper.EXPECT().CheckForResetToken(34, "reset 123").Times(testCase.checkForResetTokenCount).Return(reqId, testCase.checkForResetTokenError) + mockHelper.EXPECT().PasswordChangeForced(34, "user@example.info", "new password").Times(testCase.passwordChangeForcedCount).Return(testCase.passwordChangeForcedError) + mockHelper.EXPECT().UseToken(reqId).Times(testCase.useTokenCount).Return(testCase.userTokenError) + + handler := users.NewHandler(mockHelper, true) + router := mocks.NewTestRouter(handler, nil) // not logged in + + req := httptest.NewRequest("POST", "http://localhost/users/password/reset", 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()) + } +} + +func TestHandler_handleUserActivationPut(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + comment string + body io.Reader + getUserUser func() users.User + getUserByEmailCount int + getUserByEmailError error + checkForActivationTokenCount int + checkForActivationError error + activateUserCount int + activateUserError error + useTokenCount int + userTokenError error + expectedStatus int + expectedResponse string + }{ + { + comment: "working", + body: strings.NewReader(`{"email":"user@example.info", "activation_token":"token 123" }`), + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(1).Return(34) + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: nil, + checkForActivationTokenCount: 1, + checkForActivationError: nil, + activateUserCount: 1, + activateUserError: nil, + useTokenCount: 1, + userTokenError: nil, + expectedStatus: http.StatusAccepted, + expectedResponse: "{\"message\":\"user_activated\",\"status\":true}\n", + }, + { + comment: "decode error", + body: strings.NewReader(`{{}}`), + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(0).Return(34) + return user + }, + getUserByEmailCount: 0, + getUserByEmailError: nil, + checkForActivationTokenCount: 0, + checkForActivationError: nil, + activateUserCount: 0, + activateUserError: nil, + useTokenCount: 0, + userTokenError: nil, + expectedStatus: http.StatusUnprocessableEntity, + expectedResponse: "{\"message\":\"invalid character '{' looking for beginning of object key string\",\"status\":false}\n", + }, + { + comment: "can't find error", + body: strings.NewReader(`{"email":"user@example.info", "activation_token":"token 123" }`), + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(0).Return(34) + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: errors.New("can't find user"), + checkForActivationTokenCount: 0, + checkForActivationError: nil, + activateUserCount: 0, + activateUserError: nil, + useTokenCount: 0, + userTokenError: nil, + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"activation_forbidden\",\"status\":false}\n", + }, + { + comment: "bad token", + body: strings.NewReader(`{"email":"user@example.info", "activation_token":"token 123" }`), + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(1).Return(34) + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: nil, + checkForActivationTokenCount: 1, + checkForActivationError: errors.New("bad token"), + activateUserCount: 0, + activateUserError: nil, + useTokenCount: 0, + userTokenError: nil, + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"activation_forbidden\",\"status\":false}\n", + }, + { + comment: "can't activate", + body: strings.NewReader(`{"email":"user@example.info", "activation_token":"token 123" }`), + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(1).Return(34) + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: nil, + checkForActivationTokenCount: 1, + checkForActivationError: nil, + activateUserCount: 1, + activateUserError: errors.New("can't activate user"), + useTokenCount: 0, + userTokenError: nil, + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"can't activate user\",\"status\":false}\n", + }, + { + comment: "can't use token", + body: strings.NewReader(`{"email":"user@example.info", "activation_token":"token 123" }`), + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(1).Return(34) + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: nil, + checkForActivationTokenCount: 1, + checkForActivationError: nil, + activateUserCount: 1, + activateUserError: nil, + useTokenCount: 1, + userTokenError: errors.New("can't use token"), + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"can't use token\",\"status\":false}\n", + }, + } + + // arrange + for _, testCase := range testCases { + mockHelper := mocks.NewMockUserHelper(mockCtrl) + + reqId := 454 + + user := testCase.getUserUser() + + mockHelper.EXPECT().GetUserByEmail("user@example.info").Times(testCase.getUserByEmailCount).Return(user, testCase.getUserByEmailError) + mockHelper.EXPECT().CheckForActivationToken(34, "token 123").Times(testCase.checkForActivationTokenCount).Return(reqId, testCase.checkForActivationError) + mockHelper.EXPECT().ActivateUser(user).Times(testCase.activateUserCount).Return(testCase.activateUserError) + mockHelper.EXPECT().UseToken(reqId).Times(testCase.useTokenCount).Return(testCase.userTokenError) + + handler := users.NewHandler(mockHelper, true) + router := mocks.NewTestRouter(handler, nil) // not logged in + + req := httptest.NewRequest("POST", "http://localhost/users/activate", 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()) + } +} + +func TestHandler_handleUserActivationGet(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + comment string + url string + getUserUser func() users.User + getUserByEmailCount int + getUserByEmailError error + issueActivationRequestCount int + issueActivationRequestError error + tokenGeneratorCount int + expectedStatus int + expectedResponse string + }{ + { + comment: "working", + url: "http://localhost/users/activate?email=user@example.com", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(1).Return(34) + user.EXPECT().Email().Times(1).Return("user@example.com") + user.EXPECT().Activated().Times(1).Return(false) + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: nil, + issueActivationRequestCount: 1, + issueActivationRequestError: nil, + tokenGeneratorCount: 1, + expectedStatus: http.StatusOK, + expectedResponse: "{\"message\":\"activation_token_request_received\",\"status\":true}\n", + }, + { + comment: "no email specified", + url: "http://localhost/users/activate", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(0).Return(34) + user.EXPECT().Email().Times(0).Return("user@example.com") + user.EXPECT().Activated().Times(0).Return(false) + return user + }, + getUserByEmailCount: 0, + getUserByEmailError: nil, + issueActivationRequestCount: 0, + issueActivationRequestError: nil, + tokenGeneratorCount: 0, + expectedStatus: http.StatusUnprocessableEntity, + expectedResponse: "{\"message\":\"activation_token_missing_email\",\"status\":false}\n", + }, + { + comment: "empty email", + url: "http://localhost/users/activate?email=", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(0).Return(34) + user.EXPECT().Email().Times(0).Return("user@example.com") + user.EXPECT().Activated().Times(0).Return(false) + return user + }, + getUserByEmailCount: 0, + getUserByEmailError: nil, + issueActivationRequestCount: 0, + issueActivationRequestError: nil, + tokenGeneratorCount: 0, + expectedStatus: http.StatusUnprocessableEntity, + expectedResponse: "{\"message\":\"activation_token_missing_email\",\"status\":false}\n", + }, + { + comment: "can't find user", + url: "http://localhost/users/activate?email=user@example.com", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(0).Return(34) + user.EXPECT().Email().Times(0).Return("user@example.com") + user.EXPECT().Activated().Times(0).Return(false) + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: errors.New("can't find user"), + issueActivationRequestCount: 0, + issueActivationRequestError: nil, + tokenGeneratorCount: 0, + expectedStatus: http.StatusOK, + expectedResponse: "{\"message\":\"activation_token_request_received\",\"status\":true}\n", + }, + { + comment: "can't issue request", + url: "http://localhost/users/activate?email=user@example.com", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(1).Return(34) + user.EXPECT().Email().Times(1).Return("user@example.com") + user.EXPECT().Activated().Times(1).Return(false) + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: nil, + issueActivationRequestCount: 1, + issueActivationRequestError: errors.New("can't issue request"), + tokenGeneratorCount: 1, + expectedStatus: http.StatusServiceUnavailable, + expectedResponse: "{\"message\":\"can't issue request\",\"status\":false}\n", + }, + { + comment: "don't double issue request", + url: "http://localhost/users/activate?email=user@example.com", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(0).Return(34) + user.EXPECT().Email().Times(0).Return("user@example.com") + user.EXPECT().Activated().Times(1).Return(true) + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: nil, + issueActivationRequestCount: 0, + issueActivationRequestError: nil, + tokenGeneratorCount: 0, + expectedStatus: http.StatusOK, + expectedResponse: "{\"message\":\"activation_token_request_received\",\"status\":true}\n", + }, + } + + // arrange + for _, testCase := range testCases { + mockHelper := mocks.NewMockUserHelper(mockCtrl) + + mockHelper.EXPECT().GetUserByEmail("user@example.com").Times(testCase.getUserByEmailCount).Return(testCase.getUserUser(), testCase.getUserByEmailError) + token := "token123" + mockHelper.EXPECT().TokenGenerator().Times(testCase.tokenGeneratorCount).Return(token) + mockHelper.EXPECT().IssueActivationRequest(token, 34, "user@example.com").Times(testCase.issueActivationRequestCount).Return(testCase.issueActivationRequestError) + + handler := users.NewHandler(mockHelper, true) + router := mocks.NewTestRouter(handler, nil) // 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()) + } +} diff --git a/users/helper.go b/users/helper.go new file mode 100644 index 0000000..aa49a37 --- /dev/null +++ b/users/helper.go @@ -0,0 +1,20 @@ +package users + +//go:generate mockgen -destination=../mocks/mock_user_helper.go -package=mocks -mock_names Helper=MockUserHelper github.com/reaction-eng/restlib/users Helper + +import ( + "github.com/reaction-eng/restlib/passwords" +) + +type Helper interface { + Repo + passwords.ResetRepo + passwords.Helper + + CreateUser(user User) error + ValidateUser(user User) (bool, error) + Update(userId int, newUser User) (User, error) + PasswordChange(userId int, email string, newPassword string, oldPassword string) error + PasswordChangeForced(userId int, email string, newPassword string) error + Login(userPassword string, orgId int, user User) (User, error) +} From e77b6eaddb2c76d2cd54a2dcf45cf1ee43918a0d Mon Sep 17 00:00:00 2001 From: mcgurn Date: Sun, 3 May 2020 18:43:22 -0600 Subject: [PATCH 13/23] adding tests for the basic helper --- users/RepoSql.go | 2 + users/basicHelper.go | 58 +++--- users/basicHelper_test.go | 374 ++++++++++++++++++++++++++++++++++++++ users/helper.go | 1 - 4 files changed, 399 insertions(+), 36 deletions(-) create mode 100644 users/basicHelper_test.go diff --git a/users/RepoSql.go b/users/RepoSql.go index 5d8db5b..f8e4a0b 100644 --- a/users/RepoSql.go +++ b/users/RepoSql.go @@ -307,6 +307,8 @@ Add the user to the database */ func (repo *RepoSql) AddUser(newUser User) (User, error) { + //TODO: make sure user is not already here + //Add the info //execute the statement//(userId,name,input,flow) _, err := repo.addUserStatement.Exec(newUser.Email(), newUser.Password()) diff --git a/users/basicHelper.go b/users/basicHelper.go index 992ab4c..fc47884 100644 --- a/users/basicHelper.go +++ b/users/basicHelper.go @@ -29,18 +29,15 @@ func NewUserHelper(usersRepo Repo, passRepo passwords.ResetRepo, passwordHelper } } -/** -Static method to create a new user -*/ func (helper *BasicHelper) CreateUser(user User) error { //Make sure the info being passed in is valid - if ok, err := helper.ValidateUser(user); !ok { + if err := helper.validateUser(user); err != nil { return err } //Now hash the password - user.SetPassword(helper.Helper.HashPassword(user.Password())) + user.SetPassword(helper.HashPassword(user.Password())) //Now store it newUser, err := helper.AddUser(user) @@ -51,48 +48,25 @@ func (helper *BasicHelper) CreateUser(user User) error { } //Else issue the request - err = helper.IssueActivationRequest(helper.Helper.TokenGenerator(), newUser.Id(), newUser.Email()) - - if err != nil { - return err - } - - return nil + err = helper.IssueActivationRequest(helper.TokenGenerator(), newUser.Id(), newUser.Email()) + return err } /** Validate incoming user details to make sure it has an email address and stuff, -//TODO: add organization check */ -func (helper *BasicHelper) ValidateUser(user User) (bool, error) { +func (helper *BasicHelper) validateUser(user User) error { if !strings.Contains(user.Email(), "@") { - return false, errors.New("validate_missing_email") + return errors.New("validate_missing_email") } - //Check the password - err := helper.Helper.ValidatePassword(user.Password()) - - //If the user already exists - if err != nil { - return false, err - } - - //Now look up a possible user - user, err = helper.GetUserByEmail(user.Email()) - - //If the user already exists - if err == nil || user != nil { - return false, errors.New("validate_email_in_use") - } - - //All is good - return true, nil + return helper.ValidatePassword(user.Password()) } /** -Updates everything from the password +Updates everything but the password */ func (helper *BasicHelper) Update(userId int, newUser User) (User, error) { @@ -119,13 +93,27 @@ func (helper *BasicHelper) Update(userId int, newUser User) (User, error) { return nil, errors.New("update_forbidden") } - //Make sure we + //Don't add new orgs + if !equal(newUser.Organizations(), oldUser.Organizations()) { + return nil, errors.New("update_forbidden") + } //Now update in the repo newUser, err = helper.UpdateUser(newUser) return newUser, err +} +func equal(a, b []int) bool { + if len(a) != len(b) { + return false + } + for i, v := range a { + if v != b[i] { + return false + } + } + return true } /** diff --git a/users/basicHelper_test.go b/users/basicHelper_test.go new file mode 100644 index 0000000..08cde0a --- /dev/null +++ b/users/basicHelper_test.go @@ -0,0 +1,374 @@ +package users_test + +import ( + "errors" + "testing" + + "github.com/golang/mock/gomock" + "github.com/reaction-eng/restlib/mocks" + "github.com/reaction-eng/restlib/users" + "github.com/stretchr/testify/assert" +) + +func TestNewUserHelper(t *testing.T) { + // arrange + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockUserRepo := mocks.NewMockUserRepo(mockCtrl) + mockPasswordResetRepo := mocks.NewMockResetRepo(mockCtrl) + mockPasswordHelper := mocks.NewMockHelper(mockCtrl) + + // act + basicHelper := users.NewUserHelper(mockUserRepo, mockPasswordResetRepo, mockPasswordHelper) + + // assert + assert.NotNil(t, basicHelper) + assert.Equal(t, mockUserRepo, basicHelper.Repo) + assert.Equal(t, mockPasswordResetRepo, basicHelper.ResetRepo) + assert.Equal(t, mockPasswordHelper, basicHelper.Helper) +} + +func TestBasicHelper_CreateUser(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + comment string + user func() users.User + hashPasswordCount int + addUserCount int + addUserError error + validatePasswordCount int + validatePasswordError error + issueActivationRequestCount int + issueActivationRequestError error + tokenGeneratorCount int + expectedError error + }{ + { + comment: "working", + user: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(1).Return(34) + user.EXPECT().Email().Times(2).Return("user@example.info") + user.EXPECT().Password().Times(2).Return("password 123") + user.EXPECT().SetPassword("hashed password").Times(1) + return user + }, + validatePasswordCount: 1, + validatePasswordError: nil, + hashPasswordCount: 1, + addUserCount: 1, + addUserError: nil, + issueActivationRequestCount: 1, + issueActivationRequestError: nil, + tokenGeneratorCount: 1, + expectedError: nil, + }, + { + comment: "invalid password", + user: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(0).Return(34) + user.EXPECT().Email().Times(1).Return("user@example.info") + user.EXPECT().Password().Times(1).Return("password 123") + user.EXPECT().SetPassword("hashed password").Times(0) + return user + }, + validatePasswordCount: 1, + validatePasswordError: errors.New("invalid password"), + hashPasswordCount: 0, + addUserCount: 0, + addUserError: nil, + issueActivationRequestCount: 0, + issueActivationRequestError: nil, + tokenGeneratorCount: 0, + expectedError: errors.New("invalid password"), + }, + { + comment: "not valid email", + user: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(0).Return(34) + user.EXPECT().Email().Times(1).Return("userexample.info") + user.EXPECT().Password().Times(0).Return("password 123") + user.EXPECT().SetPassword("hashed password").Times(0) + return user + }, + validatePasswordCount: 0, + validatePasswordError: nil, + hashPasswordCount: 0, + addUserCount: 0, + addUserError: nil, + issueActivationRequestCount: 0, + issueActivationRequestError: nil, + tokenGeneratorCount: 0, + expectedError: errors.New("validate_missing_email"), + }, + { + comment: "add user error, user already there", + user: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(0).Return(34) + user.EXPECT().Email().Times(1).Return("user@example.info") + user.EXPECT().Password().Times(2).Return("password 123") + user.EXPECT().SetPassword("hashed password").Times(1) + return user + }, + validatePasswordCount: 1, + validatePasswordError: nil, + hashPasswordCount: 1, + addUserCount: 1, + addUserError: errors.New("user already here"), + issueActivationRequestCount: 0, + issueActivationRequestError: nil, + tokenGeneratorCount: 0, + expectedError: errors.New("user already here"), + }, + } + + for _, testCase := range testCases { + // arrange + user := testCase.user() + + mockUserRepo := mocks.NewMockUserRepo(mockCtrl) + mockUserRepo.EXPECT().AddUser(user).Times(testCase.addUserCount).Return(user, testCase.addUserError) + + mockPasswordResetRepo := mocks.NewMockResetRepo(mockCtrl) + token := "token 123" + mockPasswordResetRepo.EXPECT().IssueActivationRequest(token, 34, "user@example.info").Times(testCase.issueActivationRequestCount).Return(testCase.issueActivationRequestError) + + mockPasswordHelper := mocks.NewMockHelper(mockCtrl) + mockPasswordHelper.EXPECT().TokenGenerator().Times(testCase.tokenGeneratorCount).Return(token) + mockPasswordHelper.EXPECT().HashPassword("password 123").Times(testCase.hashPasswordCount).Return("hashed password") + mockPasswordHelper.EXPECT().ValidatePassword("password 123").Times(testCase.validatePasswordCount).Return(testCase.validatePasswordError) + + basicHelper := users.NewUserHelper(mockUserRepo, mockPasswordResetRepo, mockPasswordHelper) + + // act + err := basicHelper.CreateUser(user) + + // assert + assert.Equal(t, testCase.expectedError, err) + } +} + +func TestBasicHelper_Update(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + type userStruct struct { + id int + password string + email string + orgs []int + } + + testCases := []struct { + comment string + newUser userStruct + oldUser userStruct + getUserError error + getUserCount int + updateUserError error + updateUserCount int + expectedError error + expectedUser bool + }{ + { + comment: "working", + newUser: userStruct{ + id: 34, + password: "hashed password", + email: "user@example.com", + orgs: []int{23, 543}, + }, + oldUser: userStruct{ + id: 34, + password: "hashed password", + email: "user@example.com", + orgs: []int{23, 543}, + }, + getUserError: nil, + getUserCount: 1, + updateUserError: nil, + updateUserCount: 1, + expectedError: nil, + expectedUser: true, + }, + { + comment: "emails don't match", + newUser: userStruct{ + id: 34, + password: "hashed password", + email: "user@example.comm", + orgs: []int{23, 543}, + }, + oldUser: userStruct{ + id: 34, + password: "hashed password", + email: "user@example.com", + orgs: []int{23, 543}, + }, + getUserError: nil, + getUserCount: 1, + updateUserError: nil, + updateUserCount: 0, + expectedError: errors.New("update_forbidden"), + expectedUser: false, + }, + { + comment: "ids don't match", + newUser: userStruct{ + id: 33, + password: "hashed password", + email: "user@example.com", + orgs: []int{23, 543}, + }, + oldUser: userStruct{ + id: 34, + password: "hashed password", + email: "user@example.com", + orgs: []int{23, 543}, + }, + getUserError: nil, + getUserCount: 1, + updateUserError: nil, + updateUserCount: 0, + expectedError: errors.New("update_forbidden"), + expectedUser: false, + }, + { + comment: "passwords don't match", + newUser: userStruct{ + id: 34, + password: "hashed password new", + email: "user@example.com", + orgs: []int{23, 543}, + }, + oldUser: userStruct{ + id: 34, + password: "hashed password", + email: "user@example.com", + orgs: []int{23, 543}, + }, + getUserError: nil, + getUserCount: 1, + updateUserError: nil, + updateUserCount: 0, + expectedError: errors.New("update_forbidden"), + expectedUser: false, + }, + { + comment: "orgs don't match", + newUser: userStruct{ + id: 34, + password: "hashed password", + email: "user@example.com", + orgs: []int{43, 23, 543}, + }, + oldUser: userStruct{ + id: 34, + password: "hashed password", + email: "user@example.com", + orgs: []int{23, 543}, + }, + getUserError: nil, + getUserCount: 1, + updateUserError: nil, + updateUserCount: 0, + expectedError: errors.New("update_forbidden"), + expectedUser: false, + }, + { + comment: "get user error", + newUser: userStruct{ + id: 34, + password: "hashed password", + email: "user@example.com", + orgs: []int{23, 543}, + }, + oldUser: userStruct{ + id: 34, + password: "hashed password", + email: "user@example.com", + orgs: []int{23, 543}, + }, + getUserError: errors.New("get user error"), + getUserCount: 1, + updateUserError: nil, + updateUserCount: 0, + expectedError: errors.New("get user error"), + expectedUser: false, + }, + { + comment: "update user error", + newUser: userStruct{ + id: 34, + password: "hashed password", + email: "user@example.com", + orgs: []int{23, 543}, + }, + oldUser: userStruct{ + id: 34, + password: "hashed password", + email: "user@example.com", + orgs: []int{23, 543}, + }, + getUserError: nil, + getUserCount: 1, + updateUserError: errors.New("update user error"), + updateUserCount: 1, + expectedError: errors.New("update user error"), + expectedUser: true, + }, + } + + for _, testCase := range testCases { + // arrange + mockNewUser := mocks.NewMockUser(mockCtrl) + mockNewUser.EXPECT().Id().MaxTimes(1).Return(testCase.newUser.id) + mockNewUser.EXPECT().Password().MaxTimes(1).Return(testCase.newUser.password) + mockNewUser.EXPECT().Email().MaxTimes(1).Return(testCase.newUser.email) + mockNewUser.EXPECT().Organizations().MaxTimes(1).Return(testCase.newUser.orgs) + + mockOldUser := mocks.NewMockUser(mockCtrl) + mockOldUser.EXPECT().Id().MaxTimes(1).Return(testCase.oldUser.id) + mockOldUser.EXPECT().Password().MaxTimes(1).Return(testCase.oldUser.password) + mockOldUser.EXPECT().Email().MaxTimes(1).Return(testCase.oldUser.email) + mockOldUser.EXPECT().Organizations().MaxTimes(1).Return(testCase.oldUser.orgs) + + mockUserRepo := mocks.NewMockUserRepo(mockCtrl) + mockUserRepo.EXPECT().GetUser(34).Times(testCase.getUserCount).Return(mockOldUser, testCase.getUserError) + mockUserRepo.EXPECT().UpdateUser(mockNewUser).Times(testCase.updateUserCount).Return(mockNewUser, testCase.updateUserError) + + mockPasswordResetRepo := mocks.NewMockResetRepo(mockCtrl) + mockPasswordHelper := mocks.NewMockHelper(mockCtrl) + + basicHelper := users.NewUserHelper(mockUserRepo, mockPasswordResetRepo, mockPasswordHelper) + + // act + user, err := basicHelper.Update(34, mockNewUser) + + // assert + if testCase.expectedUser { + assert.Equal(t, mockNewUser, user) + } else { + assert.Nil(t, user) + } + assert.Equal(t, testCase.expectedError, err) + } +} + +func TestBasicHelper_PasswordChange(t *testing.T) { + +} + +func TestBasicHelper_PasswordChangeForced(t *testing.T) { + assert.Fail(t, "not done") +} + +func TestBasicHelper_Login(t *testing.T) { + assert.Fail(t, "not done") +} diff --git a/users/helper.go b/users/helper.go index aa49a37..5cf5867 100644 --- a/users/helper.go +++ b/users/helper.go @@ -12,7 +12,6 @@ type Helper interface { passwords.Helper CreateUser(user User) error - ValidateUser(user User) (bool, error) Update(userId int, newUser User) (User, error) PasswordChange(userId int, email string, newPassword string, oldPassword string) error PasswordChangeForced(userId int, email string, newPassword string) error From d4bd39eb3a986ccb1392854e818c73450f7d9d66 Mon Sep 17 00:00:00 2001 From: mcgurn Date: Wed, 6 May 2020 21:39:03 -0600 Subject: [PATCH 14/23] add migrations for users and testing for user repo sql --- main.go | 9 +- migrations/mysql/3_initial_users.sql | 5 + migrations/postgres/3_initial_users.sql | 5 + mocks/mock_helper.go | 8 +- mocks/mock_user.go | 40 +- mocks/mock_user_helper.go | 53 +- mocks/mock_users_repo.go | 30 +- passwords/basicHelper.go | 9 +- passwords/basicHelper_test.go | 12 +- passwords/helper.go | 2 +- users/Facebook.go | 2 +- users/Google.go | 2 +- users/RepoMemory.go | 145 ----- users/RepoMongo.go | 7 +- users/User.go | 3 +- users/User_test.go | 129 ---- users/basicHelper.go | 54 +- users/basicHelper_test.go | 620 ++++++++++++++++++- users/basicUser.go | 27 +- users/handlers.go | 2 +- users/handlers_test.go | 4 +- users/{Repo.go => repo.go} | 3 +- users/{RepoSql.go => repoSql.go} | 209 ++----- users/repoSql_test.go | 775 ++++++++++++++++++++++++ 24 files changed, 1558 insertions(+), 597 deletions(-) create mode 100644 migrations/mysql/3_initial_users.sql create mode 100644 migrations/postgres/3_initial_users.sql delete mode 100644 users/RepoMemory.go rename users/{Repo.go => repo.go} (92%) rename users/{RepoSql.go => repoSql.go} (54%) create mode 100644 users/repoSql_test.go 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/migrations/mysql/3_initial_users.sql b/migrations/mysql/3_initial_users.sql new file mode 100644 index 0000000..9ced578 --- /dev/null +++ b/migrations/mysql/3_initial_users.sql @@ -0,0 +1,5 @@ +-- +migrate Up +CREATE TABLE IF NOT EXISTS users (id int NOT NULL AUTO_INCREMENT, email TEXT, password TEXT, activation Date, PRIMARY KEY (id) ) + +-- +migrate Down +DROP TABLE users; \ 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..72e873a --- /dev/null +++ b/migrations/postgres/3_initial_users.sql @@ -0,0 +1,5 @@ +-- +migrate Up +CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, email TEXT NOT NULL, password TEXT NOT NULL, activation Date) + +-- +migrate Down +DROP TABLE users; \ No newline at end of file diff --git a/mocks/mock_helper.go b/mocks/mock_helper.go index 93f1b81..b82d566 100644 --- a/mocks/mock_helper.go +++ b/mocks/mock_helper.go @@ -47,17 +47,17 @@ func (mr *MockHelperMockRecorder) ComparePasswords(arg0, arg1 interface{}) *gomo } // CreateJWTToken mocks base method -func (m *MockHelper) CreateJWTToken(arg0 int, arg1 string) string { +func (m *MockHelper) CreateJWTToken(arg0, arg1 int, arg2 string) string { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateJWTToken", arg0, arg1) + 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 interface{}) *gomock.Call { +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) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateJWTToken", reflect.TypeOf((*MockHelper)(nil).CreateJWTToken), arg0, arg1, arg2) } // HashPassword mocks base method diff --git a/mocks/mock_user.go b/mocks/mock_user.go index 3a5c49c..4d40def 100644 --- a/mocks/mock_user.go +++ b/mocks/mock_user.go @@ -46,18 +46,6 @@ func (mr *MockUserMockRecorder) Activated() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Activated", reflect.TypeOf((*MockUser)(nil).Activated)) } -// AddOrganization mocks base method -func (m *MockUser) AddOrganization(arg0 int) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "AddOrganization", arg0) -} - -// AddOrganization indicates an expected call of AddOrganization -func (mr *MockUserMockRecorder) AddOrganization(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddOrganization", reflect.TypeOf((*MockUser)(nil).AddOrganization), arg0) -} - // Email mocks base method func (m *MockUser) Email() string { m.ctrl.T.Helper() @@ -128,18 +116,6 @@ func (mr *MockUserMockRecorder) PasswordLogin() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PasswordLogin", reflect.TypeOf((*MockUser)(nil).PasswordLogin)) } -// RemoveOrganization mocks base method -func (m *MockUser) RemoveOrganization(arg0 int) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "RemoveOrganization", arg0) -} - -// RemoveOrganization indicates an expected call of RemoveOrganization -func (mr *MockUserMockRecorder) RemoveOrganization(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveOrganization", reflect.TypeOf((*MockUser)(nil).RemoveOrganization), arg0) -} - // SetEmail mocks base method func (m *MockUser) SetEmail(arg0 string) { m.ctrl.T.Helper() @@ -164,6 +140,22 @@ func (mr *MockUserMockRecorder) SetId(arg0 interface{}) *gomock.Call { 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() diff --git a/mocks/mock_user_helper.go b/mocks/mock_user_helper.go index 2805666..4ab1cf5 100644 --- a/mocks/mock_user_helper.go +++ b/mocks/mock_user_helper.go @@ -5,9 +5,10 @@ package mocks import ( + reflect "reflect" + gomock "github.com/golang/mock/gomock" users "github.com/reaction-eng/restlib/users" - reflect "reflect" ) // MockUserHelper is a mock of Helper interface @@ -119,17 +120,17 @@ func (mr *MockUserHelperMockRecorder) ComparePasswords(arg0, arg1 interface{}) * } // CreateJWTToken mocks base method -func (m *MockUserHelper) CreateJWTToken(arg0 int, arg1 string) string { +func (m *MockUserHelper) CreateJWTToken(arg0, arg1 int, arg2 string) string { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateJWTToken", arg0, arg1) + 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 interface{}) *gomock.Call { +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) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateJWTToken", reflect.TypeOf((*MockUserHelper)(nil).CreateJWTToken), arg0, arg1, arg2) } // CreateUser mocks base method @@ -218,34 +219,19 @@ func (mr *MockUserHelperMockRecorder) IssueResetRequest(arg0, arg1, arg2 interfa return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IssueResetRequest", reflect.TypeOf((*MockUserHelper)(nil).IssueResetRequest), arg0, arg1, arg2) } -// ListAllActiveUsers mocks base method -func (m *MockUserHelper) ListAllActiveUsers() ([]int, error) { +// ListUsers mocks base method +func (m *MockUserHelper) ListUsers(arg0 bool, arg1 []int) ([]int, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListAllActiveUsers") + ret := m.ctrl.Call(m, "ListUsers", arg0, arg1) ret0, _ := ret[0].([]int) ret1, _ := ret[1].(error) return ret0, ret1 } -// ListAllActiveUsers indicates an expected call of ListAllActiveUsers -func (mr *MockUserHelperMockRecorder) ListAllActiveUsers() *gomock.Call { +// ListUsers indicates an expected call of ListUsers +func (mr *MockUserHelperMockRecorder) ListAllUsers(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAllActiveUsers", reflect.TypeOf((*MockUserHelper)(nil).ListAllActiveUsers)) -} - -// ListAllUsers mocks base method -func (m *MockUserHelper) ListAllUsers() ([]int, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListAllUsers") - ret0, _ := ret[0].([]int) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListAllUsers indicates an expected call of ListAllUsers -func (mr *MockUserHelperMockRecorder) ListAllUsers() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAllUsers", reflect.TypeOf((*MockUserHelper)(nil).ListAllUsers)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListUsers", reflect.TypeOf((*MockUserHelper)(nil).ListUsers), arg0, arg1) } // Login mocks base method @@ -392,18 +378,3 @@ func (mr *MockUserHelperMockRecorder) ValidateToken(arg0 interface{}) *gomock.Ca mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateToken", reflect.TypeOf((*MockUserHelper)(nil).ValidateToken), arg0) } - -// ValidateUser mocks base method -func (m *MockUserHelper) ValidateUser(arg0 users.User) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ValidateUser", arg0) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ValidateUser indicates an expected call of ValidateUser -func (mr *MockUserHelperMockRecorder) ValidateUser(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateUser", reflect.TypeOf((*MockUserHelper)(nil).ValidateUser), arg0) -} diff --git a/mocks/mock_users_repo.go b/mocks/mock_users_repo.go index 9c89ae6..a85c693 100644 --- a/mocks/mock_users_repo.go +++ b/mocks/mock_users_repo.go @@ -5,9 +5,10 @@ package mocks import ( + reflect "reflect" + gomock "github.com/golang/mock/gomock" users "github.com/reaction-eng/restlib/users" - reflect "reflect" ) // MockUserRepo is a mock of Repo interface @@ -92,34 +93,19 @@ func (mr *MockUserRepoMockRecorder) GetUserByEmail(arg0 interface{}) *gomock.Cal return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByEmail", reflect.TypeOf((*MockUserRepo)(nil).GetUserByEmail), arg0) } -// ListAllActiveUsers mocks base method -func (m *MockUserRepo) ListAllActiveUsers() ([]int, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListAllActiveUsers") - ret0, _ := ret[0].([]int) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListAllActiveUsers indicates an expected call of ListAllActiveUsers -func (mr *MockUserRepoMockRecorder) ListAllActiveUsers() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAllActiveUsers", reflect.TypeOf((*MockUserRepo)(nil).ListAllActiveUsers)) -} - -// ListAllUsers mocks base method -func (m *MockUserRepo) ListAllUsers() ([]int, error) { +// ListUsers mocks base method +func (m *MockUserRepo) ListUsers(arg0 bool, arg1 []int) ([]int, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListAllUsers") + ret := m.ctrl.Call(m, "ListUsers", arg0, arg1) ret0, _ := ret[0].([]int) ret1, _ := ret[1].(error) return ret0, ret1 } -// ListAllUsers indicates an expected call of ListAllUsers -func (mr *MockUserRepoMockRecorder) ListAllUsers() *gomock.Call { +// ListUsers indicates an expected call of ListUsers +func (mr *MockUserRepoMockRecorder) ListAllUsers(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAllUsers", reflect.TypeOf((*MockUserRepo)(nil).ListAllUsers)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListUsers", reflect.TypeOf((*MockUserRepo)(nil).ListUsers), arg0, arg1) } // NewEmptyUser mocks base method diff --git a/passwords/basicHelper.go b/passwords/basicHelper.go index 2552d9e..4c6a650 100644 --- a/passwords/basicHelper.go +++ b/passwords/basicHelper.go @@ -23,8 +23,9 @@ type BasicHelper struct { JWT claims struct */ type Token struct { - UserId int - Email string + UserId int + OrganizationId int + Email string jwt.StandardClaims } @@ -57,10 +58,10 @@ func (helper *BasicHelper) HashPassword(password string) string { /** 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) diff --git a/passwords/basicHelper_test.go b/passwords/basicHelper_test.go index c82d253..0c2cded 100644 --- a/passwords/basicHelper_test.go +++ b/passwords/basicHelper_test.go @@ -104,23 +104,27 @@ func TestBasicHelper_HashPassword(t *testing.T) { func TestBasicHelper_CreateJWTToken(t *testing.T) { testCases := []struct { userId int + orgId int email string expectedResult string }{ { 42, + 65, "example@example.com", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjQyLCJFbWFpbCI6ImV4YW1wbGVAZXhhbXBsZS5jb20ifQ.Ah3uKFRpjKpfBys0Rmq3MdSwwxkPj_WLBqBy9P21heI", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjQyLCJPcmdhbml6YXRpb25JZCI6NjUsIkVtYWlsIjoiZXhhbXBsZUBleGFtcGxlLmNvbSJ9.8xvP_tiVqrowq85_t4eoJqh0PXJGqOY1mG6ixKcFpqw", }, { 102, + 23, "example2@example.com", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEwMiwiRW1haWwiOiJleGFtcGxlMkBleGFtcGxlLmNvbSJ9.tsBA8HJeN-Zhamyx6AvcTlmp5P_5ihTYsusln-50MBg", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEwMiwiT3JnYW5pemF0aW9uSWQiOjIzLCJFbWFpbCI6ImV4YW1wbGUyQGV4YW1wbGUuY29tIn0.s0IMktQm5I3DJ9Bixdyd42-q3dEbj6xAFz_v7AOaLRY", }, { 98, + 346, "matt@example.com", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjk4LCJFbWFpbCI6Im1hdHRAZXhhbXBsZS5jb20ifQ.zbpK1ZSeOTrsvSscE7KJqdhHfMgiOSHiu_2jOBsOLyA", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjk4LCJPcmdhbml6YXRpb25JZCI6MzQ2LCJFbWFpbCI6Im1hdHRAZXhhbXBsZS5jb20ifQ.pzf0sT2F77gH-1Ghqw_bwWrxSDbpc85FD1nXxBByHQc", }, } @@ -132,7 +136,7 @@ func TestBasicHelper_CreateJWTToken(t *testing.T) { helper := setupBasicHelper(t, mockCtrl) // act - output := helper.CreateJWTToken(testCase.userId, testCase.email) + output := helper.CreateJWTToken(testCase.userId, testCase.orgId, testCase.email) // assert assert.Equal(t, testCase.expectedResult, output) diff --git a/passwords/helper.go b/passwords/helper.go index feb25a8..8beaf5e 100644 --- a/passwords/helper.go +++ b/passwords/helper.go @@ -7,7 +7,7 @@ package passwords 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) diff --git a/users/Facebook.go b/users/Facebook.go index cddc094..3601171 100644 --- a/users/Facebook.go +++ b/users/Facebook.go @@ -244,7 +244,7 @@ func (fbHandler *FacebookHandler) handleUserLoginFacebook(w http.ResponseWriter, } //Create JWT token and Store the token in the response - user.SetToken(fbHandler.helper.CreateJWTToken(user.Id(), user.Email())) + user.SetToken(fbHandler.helper.CreateJWTToken(user.Id(), -1, user.Email())) //Check to see if the user was created if err == nil { diff --git a/users/Google.go b/users/Google.go index c8537e5..bda68ef 100644 --- a/users/Google.go +++ b/users/Google.go @@ -156,7 +156,7 @@ func (gHandler *GoogleHandler) handleUserLoginGoogle(w http.ResponseWriter, r *h } //Create JWT token and Store the token in the response - user.SetToken(gHandler.helper.CreateJWTToken(user.Id(), user.Email())) + user.SetToken(gHandler.helper.CreateJWTToken(user.Id(), -1, user.Email())) //Check to see if the user was created if err == nil { diff --git a/users/RepoMemory.go b/users/RepoMemory.go deleted file mode 100644 index b7a6b4a..0000000 --- a/users/RepoMemory.go +++ /dev/null @@ -1,145 +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 users - -import ( - "errors" - //"log" -) - -/** -Define a struct for Repo for use with users -*/ -type RepoMemory struct { - // The current id - currentId int - - //A list of the sers - usersList []User -} - -//Provide a method to make a new UserRepoMemory -func NewRepoMemory() *RepoMemory { - //Define a new repo - newRepo := RepoMemory{ - 0, - make([]User, 0), - } - - //Return a point - return &newRepo - -} - -/** -Look up the user and return if they were found -*/ -func (repo *RepoMemory) GetUserByEmail(email string) (User, error) { - //March over each - for _, v := range repo.usersList { - //Check the email - if v.Email() == email { - return v, nil - } - } - - return nil, errors.New("no user with email") -} - -/** -Look up the user by id and return if they were found -*/ -func (repo *RepoMemory) GetUser(id int) (User, error) { - //March over each - for _, v := range repo.usersList { - //Check the email - if v.Id() == id { - return v, nil - } - } - - return nil, errors.New("no user with id") -} - -/** -Add the user to the database -*/ -func (repo *RepoMemory) AddUser(t User) (User, error) { - repo.currentId += 1 - t.SetId(repo.currentId) - - repo.usersList = append(repo.usersList, t) - return t, nil -} - -/** -Clean up the database, nothing much to do -*/ -func (repo *RepoMemory) CleanUp() { - -} - -/** -Clean up the database, nothing much to do -*/ -func (repo *RepoMemory) NewEmptyUser() User { - return &BasicUser{} -} - -/** -Update the user table. No checks are made here, -*/ -func (repo *RepoMemory) 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) - //} - - // TODO: Doesn't do anything - - return user, nil -} - -/** -List all users -*/ -func (repo *RepoMemory) ListAllUsers() ([]int, error) { - list := make([]int, 0) - - for _, user := range repo.usersList { - list = append(list, user.Id()) - } - - return list, nil -} - -func (repo *RepoMemory) ListAllActiveUsers() ([]int, error) { - return repo.ListAllUsers() -} - -/** -List all users -*/ -func (repo *RepoMemory) ActivateUser(user User) error { - return nil -} - -/** -Activate User -*/ - -//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/RepoMongo.go b/users/RepoMongo.go index 81b4693..f98d541 100644 --- a/users/RepoMongo.go +++ b/users/RepoMongo.go @@ -5,11 +5,12 @@ package users import ( "context" + "log" + "time" + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" - "log" - "time" ) /** @@ -89,7 +90,7 @@ Get the user with the ID. An error is thrown is not found ///** //List all users //*/ -//func (repo *RepoMongo)ListAllUsers() ([]int, error){} +//func (repo *RepoMongo)ListUsers() ([]int, error){} //Connect to a db, returns pointer to db func ConnectToDB(locOfDB string, dbName string) *mongo.Database { diff --git a/users/User.go b/users/User.go index 4cdb824..8d43dca 100644 --- a/users/User.go +++ b/users/User.go @@ -22,8 +22,7 @@ type User interface { Token() string SetToken(token string) - AddOrganization(org int) - RemoveOrganization(org int) + SetOrganizations(organizations ...int) Organizations() []int //Check if the user was activated diff --git a/users/User_test.go b/users/User_test.go index c4a8e76..965df55 100644 --- a/users/User_test.go +++ b/users/User_test.go @@ -2,132 +2,3 @@ // Use of this source code is governed by the MIT license in the file LICENSE.txt. package users_test - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/reaction-eng/restlib/configuration" - - "github.com/reaction-eng/restlib/middleware" - "github.com/reaction-eng/restlib/passwords" - "github.com/reaction-eng/restlib/routing" - "github.com/reaction-eng/restlib/users" -) - -type routingEnv struct { - router *routing.MuxRouter -} - -func TestUserRoutes(t *testing.T) { - - //Define the list of routes we testing - var routes = []struct { - method string - path string - expectedCode int - }{ //Now define with - {"GET", "/api/users", http.StatusOK}, - {"PUT", "/users/", http.StatusForbidden}, - } - - //Now run over each test as a logged out user - for _, rr := range routes { - //Get the default env - env := getDefaultEnv(t) - - //Now run the test - t.Run("logged out "+rr.path, func(t *testing.T) { - - //In the test function build the request - req, err := http.NewRequest(rr.method, rr.path, nil) - if err != nil { - t.Fatal(err) - } - - // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response. - rec := httptest.NewRecorder() - - //Get the router from the env and do the action - env.router.ServeHTTP(rec, req) - - //Make sure the status is correct - if rec.Result().StatusCode != rr.expectedCode { - t.Errorf("recived status code %d, expected %d", rec.Result().StatusCode, rr.expectedCode) - - } - }) - } - // Create a request to pass to our handler. We don't have any query parameters for now, so we'll - // pass 'nil' as the third parameter. - - //// Check the status code is what we expect. - //if status := rr.Code; status != http.StatusOK { - // t.Errorf("handler returned wrong status code: got %v want %v", - // status, http.StatusOK) - //} - // - //// Check the response body is what we expect. - //expected := `{"alive": true}` - //if rr.Body.String() != expected { - // t.Errorf("handler returned unexpected body: got %v want %v", - // rr.Body.String(), expected) - //} - -} - -/** -Builds the default routing env -*/ -func getDefaultEnv(t *testing.T) *routingEnv { - - //Build a config string - configString := "{\"token_password\": \"RvUP*b7fj9JPJ0*OQ9FlCW%Gg7vNTJWfvV7aQf@u9gWuYQ!S@e9SegAYjh!G%V7btMuGC8g29$qOw\"}" - - //Define a memory repo - userRepo := users.NewRepoMemory() - - //Make a basic - config, _ := configuration.NewJson(configString) - passHelper, _ := passwords.NewBasicHelper(config) - - //Make a user helper - helper := users.NewUserHelper(userRepo, nil, passHelper) - - //Add some default users - userOne := users.BasicUser{} - userOne.SetEmail("one@example.com") - userOne.SetPassword(passHelper.HashPassword("123456")) - _, err := userRepo.AddUser(&userOne) - - userTwo := users.BasicUser{} - userTwo.SetEmail("two@example.com") - userTwo.SetPassword(passHelper.HashPassword("789012")) - _, err = userRepo.AddUser(&userTwo) - - if err != nil { - t.Error(err) - } - - //Define a new router repo - //We also need to handle requests about users, - userHandler := users.NewHandler(helper, false) - - //Define the router, by in the routes specific to this project, and others - router := routing.NewRouter(nil, nil, nil, userHandler) - - //Add in middleware/filter that respons to CORS - router.Use(middleware.MakeCORSMiddlewareFunc()) //Make sure to add the cross site permission first - - //Add in middleware/filter that checks for user mysql - router.Use(middleware.MakeJwtMiddlewareFunc(router, userRepo, nil, passHelper)) - - //Define the routing env - env := routingEnv{ - router: router, - } - - return &env - -} diff --git a/users/basicHelper.go b/users/basicHelper.go index fc47884..d604e98 100644 --- a/users/basicHelper.go +++ b/users/basicHelper.go @@ -126,6 +126,9 @@ func (helper *BasicHelper) PasswordChange(userId int, email string, newPassword //Load up the user oldUser, err := helper.GetUser(userId) + if err != nil { + return err + } //Make sure the user can login with password if !oldUser.PasswordLogin() { @@ -138,15 +141,15 @@ func (helper *BasicHelper) PasswordChange(userId int, email string, newPassword } //Make sure the old password matches - passwordsMath := helper.Helper.ComparePasswords(oldUser.Password(), oldPassword) + passwordsMatch := helper.ComparePasswords(oldUser.Password(), oldPassword) //Make sure that the emails match - if !passwordsMath { + if !passwordsMatch { return errors.New("password_change_forbidden") } //Make sure the new password is valid - err = helper.Helper.ValidatePassword(newPassword) + err = helper.ValidatePassword(newPassword) //If the password is bad if err != nil { @@ -154,7 +157,7 @@ func (helper *BasicHelper) PasswordChange(userId int, email string, newPassword } //So it looks like we can update it, so hash the new password - oldUser.SetPassword(helper.Helper.HashPassword(newPassword)) + oldUser.SetPassword(helper.HashPassword(newPassword)) //Now update in the repo _, err = helper.UpdateUser(oldUser) @@ -163,9 +166,6 @@ func (helper *BasicHelper) PasswordChange(userId int, email string, newPassword } -/** -Updates everything from the password -*/ func (helper *BasicHelper) PasswordChangeForced(userId int, email string, newPassword string) error { //Clean up the email @@ -173,14 +173,17 @@ func (helper *BasicHelper) PasswordChangeForced(userId int, email string, newPas //Load up the user oldUser, err := helper.GetUser(userId) + if err != nil { + return err + } - //Make sure the user can login with password - //if !oldUser.PasswordLogin() { - // return errors.New("user_password_login_forbidden") - //} + //Make sure that the emails match + if email != oldUser.Email() { + return errors.New("password_change_forbidden") + } //Make sure the new password is valid - err = helper.Helper.ValidatePassword(newPassword) + err = helper.ValidatePassword(newPassword) //If the password is bad if err != nil { @@ -188,7 +191,7 @@ func (helper *BasicHelper) PasswordChangeForced(userId int, email string, newPas } //So it looks like we can update it, so hash the new password - oldUser.SetPassword(helper.Helper.HashPassword(newPassword)) + oldUser.SetPassword(helper.HashPassword(newPassword)) //Now update in the repo _, err = helper.UpdateUser(oldUser) @@ -200,7 +203,7 @@ func (helper *BasicHelper) PasswordChangeForced(userId int, email string, newPas /** Login in the user */ -func (helper *BasicHelper) Login(userPassword string, orgId int, user User) (User, error) { +func (helper *BasicHelper) Login(userPassword string, organizationId int, user User) (User, error) { //Make sure the user can login with password if !user.PasswordLogin() { @@ -212,8 +215,12 @@ func (helper *BasicHelper) Login(userPassword string, orgId int, user User) (Use return nil, errors.New("user_not_activated") } - //Make sure the new password is valid - err := helper.Helper.ValidatePassword(userPassword) + // make sure user is in org + if !inList(user.Organizations(), organizationId) { + return nil, errors.New("user_not_in_organization") + } + + err := helper.ValidatePassword(userPassword) //If the password is bad if err != nil { @@ -221,18 +228,27 @@ func (helper *BasicHelper) Login(userPassword string, orgId int, user User) (Use } //Now see if we login - passwordsMath := helper.Helper.ComparePasswords(user.Password(), userPassword) + passwordsMatch := helper.ComparePasswords(user.Password(), userPassword) //Blank out the password before returning user.SetPassword("") //If they do not match - if !passwordsMath { + if !passwordsMatch { return nil, errors.New("login_invalid_password") } //Create JWT token and Store the token in the response - user.SetToken(helper.Helper.CreateJWTToken(user.Id(), user.Email())) + user.SetToken(helper.CreateJWTToken(user.Id(), organizationId, user.Email())) return user, nil } + +func inList(organizationList []int, organization int) bool { + for _, org := range organizationList { + if org == organization { + return true + } + } + return false +} diff --git a/users/basicHelper_test.go b/users/basicHelper_test.go index 08cde0a..26b4287 100644 --- a/users/basicHelper_test.go +++ b/users/basicHelper_test.go @@ -106,6 +106,46 @@ func TestBasicHelper_CreateUser(t *testing.T) { tokenGeneratorCount: 0, expectedError: errors.New("validate_missing_email"), }, + { + comment: "empty email", + user: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(0).Return(34) + user.EXPECT().Email().Times(1).Return("") + user.EXPECT().Password().Times(0).Return("password 123") + user.EXPECT().SetPassword("hashed password").Times(0) + return user + }, + validatePasswordCount: 0, + validatePasswordError: nil, + hashPasswordCount: 0, + addUserCount: 0, + addUserError: nil, + issueActivationRequestCount: 0, + issueActivationRequestError: nil, + tokenGeneratorCount: 0, + expectedError: errors.New("validate_missing_email"), + }, + { + comment: "all white space", + user: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(0).Return(34) + user.EXPECT().Email().Times(1).Return(" ") + user.EXPECT().Password().Times(0).Return("password 123") + user.EXPECT().SetPassword("hashed password").Times(0) + return user + }, + validatePasswordCount: 0, + validatePasswordError: nil, + hashPasswordCount: 0, + addUserCount: 0, + addUserError: nil, + issueActivationRequestCount: 0, + issueActivationRequestError: nil, + tokenGeneratorCount: 0, + expectedError: errors.New("validate_missing_email"), + }, { comment: "add user error, user already there", user: func() users.User { @@ -362,13 +402,585 @@ func TestBasicHelper_Update(t *testing.T) { } func TestBasicHelper_PasswordChange(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() -} + testCases := []struct { + comment string + userId int + email string + newPassword string + oldPassword string + getUserUser func() users.User + getUserError error + comparePasswordsCount int + comparePasswordsResponse bool + validatePasswordCount int + validatePasswordError error + hashPasswordCount int + hashPasswordResponse string + updateUserCount int + updateUserError error + expectedError error + }{ + { + comment: "working", + userId: 34, + email: "user@example.info", + newPassword: "new password", + oldPassword: "old password", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Email().Times(1).Return("user@example.info") + user.EXPECT().Password().Times(1).Return("hashed password") + user.EXPECT().SetPassword("hashed password").Times(1) + user.EXPECT().PasswordLogin().Times(1).Return(true) + return user + }, + getUserError: nil, + comparePasswordsCount: 1, + comparePasswordsResponse: true, + validatePasswordCount: 1, + validatePasswordError: nil, + hashPasswordCount: 1, + hashPasswordResponse: "hashed password", + updateUserCount: 1, + updateUserError: nil, + expectedError: nil, + }, + { + comment: "working with different case email", + userId: 34, + email: " uSeR@example.inFo ", + newPassword: "new password", + oldPassword: "old password", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Email().Times(1).Return("user@example.info") + user.EXPECT().Password().Times(1).Return("hashed password") + user.EXPECT().SetPassword("hashed password").Times(1) + user.EXPECT().PasswordLogin().Times(1).Return(true) + return user + }, + getUserError: nil, + comparePasswordsCount: 1, + comparePasswordsResponse: true, + validatePasswordCount: 1, + validatePasswordError: nil, + hashPasswordCount: 1, + hashPasswordResponse: "hashed password", + updateUserCount: 1, + updateUserError: nil, + expectedError: nil, + }, + { + comment: "no user", + userId: 34, + email: " uSeR@example.inFo ", + newPassword: "new password", + oldPassword: "old password", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Email().Times(0).Return("user@example.info") + user.EXPECT().Password().Times(0).Return("hashed password") + user.EXPECT().SetPassword("hashed password").Times(0) + user.EXPECT().PasswordLogin().Times(0).Return(true) + return user + }, + getUserError: errors.New("missing user"), + comparePasswordsCount: 0, + comparePasswordsResponse: true, + validatePasswordCount: 0, + validatePasswordError: nil, + hashPasswordCount: 0, + hashPasswordResponse: "hashed password", + updateUserCount: 0, + updateUserError: nil, + expectedError: errors.New("missing user"), + }, + { + comment: "no password access", + userId: 34, + email: " uSeR@example.inFo ", + newPassword: "new password", + oldPassword: "old password", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Email().Times(0).Return("user@example.info") + user.EXPECT().Password().Times(0).Return("hashed password") + user.EXPECT().SetPassword("hashed password").Times(0) + user.EXPECT().PasswordLogin().Times(1).Return(false) + return user + }, + getUserError: nil, + comparePasswordsCount: 0, + comparePasswordsResponse: true, + validatePasswordCount: 0, + validatePasswordError: nil, + hashPasswordCount: 0, + hashPasswordResponse: "hashed password", + updateUserCount: 0, + updateUserError: nil, + expectedError: errors.New("user_password_login_forbidden"), + }, { + comment: "emails don't match", + userId: 34, + email: " uSeR@example.inFoo ", + newPassword: "new password", + oldPassword: "old password", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Email().Times(1).Return("user@example.info") + user.EXPECT().Password().Times(0).Return("hashed password") + user.EXPECT().SetPassword("hashed password").Times(0) + user.EXPECT().PasswordLogin().Times(1).Return(true) + return user + }, + getUserError: nil, + comparePasswordsCount: 0, + comparePasswordsResponse: true, + validatePasswordCount: 0, + validatePasswordError: nil, + hashPasswordCount: 0, + hashPasswordResponse: "hashed password", + updateUserCount: 0, + updateUserError: nil, + expectedError: errors.New("password_change_forbidden"), + }, + { + comment: "wrong password", + userId: 34, + email: " uSeR@example.inFo ", + newPassword: "new password", + oldPassword: "old password", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Email().Times(1).Return("user@example.info") + user.EXPECT().Password().Times(1).Return("hashed password") + user.EXPECT().SetPassword("hashed password").Times(0) + user.EXPECT().PasswordLogin().Times(1).Return(true) + return user + }, + getUserError: nil, + comparePasswordsCount: 1, + comparePasswordsResponse: false, + validatePasswordCount: 0, + validatePasswordError: nil, + hashPasswordCount: 0, + hashPasswordResponse: "hashed password", + updateUserCount: 0, + updateUserError: nil, + expectedError: errors.New("password_change_forbidden"), + }, + { + comment: "invalid password", + userId: 34, + email: " uSeR@example.inFo ", + newPassword: "new password", + oldPassword: "old password", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Email().Times(1).Return("user@example.info") + user.EXPECT().Password().Times(1).Return("hashed password") + user.EXPECT().SetPassword("hashed password").Times(0) + user.EXPECT().PasswordLogin().Times(1).Return(true) + return user + }, + getUserError: nil, + comparePasswordsCount: 1, + comparePasswordsResponse: true, + validatePasswordCount: 1, + validatePasswordError: errors.New("bad password"), + hashPasswordCount: 0, + hashPasswordResponse: "hashed password", + updateUserCount: 0, + updateUserError: nil, + expectedError: errors.New("bad password"), + }, + { + comment: "can't update user", + userId: 34, + email: " uSeR@example.inFo ", + newPassword: "new password", + oldPassword: "old password", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Email().Times(1).Return("user@example.info") + user.EXPECT().Password().Times(1).Return("hashed password") + user.EXPECT().SetPassword("hashed password").Times(1) + user.EXPECT().PasswordLogin().Times(1).Return(true) + return user + }, + getUserError: nil, + comparePasswordsCount: 1, + comparePasswordsResponse: true, + validatePasswordCount: 1, + validatePasswordError: nil, + hashPasswordCount: 1, + hashPasswordResponse: "hashed password", + updateUserCount: 1, + updateUserError: errors.New("can't update user"), + expectedError: errors.New("can't update user"), + }, + } -func TestBasicHelper_PasswordChangeForced(t *testing.T) { - assert.Fail(t, "not done") + for _, testCase := range testCases { + // arrange + user := testCase.getUserUser() + + mockUserRepo := mocks.NewMockUserRepo(mockCtrl) + mockUserRepo.EXPECT().GetUser(testCase.userId).Times(1).Return(user, testCase.getUserError) + mockUserRepo.EXPECT().UpdateUser(user).Times(testCase.updateUserCount).Return(nil, testCase.updateUserError) + + mockPasswordResetRepo := mocks.NewMockResetRepo(mockCtrl) + + mockPasswordHelper := mocks.NewMockHelper(mockCtrl) + mockPasswordHelper.EXPECT().ComparePasswords("hashed password", testCase.oldPassword).Times(testCase.comparePasswordsCount).Return(testCase.comparePasswordsResponse) + mockPasswordHelper.EXPECT().ValidatePassword("new password").Times(testCase.validatePasswordCount).Return(testCase.validatePasswordError) + mockPasswordHelper.EXPECT().HashPassword("new password").Times(testCase.hashPasswordCount).Return("hashed password") + + basicHelper := users.NewUserHelper(mockUserRepo, mockPasswordResetRepo, mockPasswordHelper) + + // act + err := basicHelper.PasswordChange(testCase.userId, testCase.email, testCase.newPassword, testCase.oldPassword) + + // assert + assert.Equal(t, testCase.expectedError, err) + } } func TestBasicHelper_Login(t *testing.T) { - assert.Fail(t, "not done") + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + comment string + getUserUser func() users.User + validatePasswordCount int + validatePasswordError error + comparePasswordsCount int + comparePasswordsResponse bool + createJWTTokenCount int + expectedError error + }{ + { + comment: "working", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().PasswordLogin().Times(1).Return(true) + user.EXPECT().Activated().Times(1).Return(true) + user.EXPECT().Password().Times(1).Return("hashed password") + user.EXPECT().SetPassword("").Times(1) + user.EXPECT().Id().Times(1).Return(34) + user.EXPECT().Email().Times(1).Return("user@example.info") + user.EXPECT().SetToken("token").Times(1) + user.EXPECT().Organizations().Times(1).Return([]int{3, 454, 534}) + return user + }, + validatePasswordCount: 1, + validatePasswordError: nil, + comparePasswordsCount: 1, + comparePasswordsResponse: true, + createJWTTokenCount: 1, + expectedError: nil, + }, + { + comment: "wrong org", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().PasswordLogin().Times(1).Return(true) + user.EXPECT().Activated().Times(1).Return(true) + user.EXPECT().Password().Times(0).Return("hashed password") + user.EXPECT().SetPassword("").Times(0) + user.EXPECT().Id().Times(0).Return(34) + user.EXPECT().Email().Times(0).Return("user@example.info") + user.EXPECT().SetToken("token").Times(0) + user.EXPECT().Organizations().Times(1).Return([]int{3, 2, 534}) + return user + }, + validatePasswordCount: 0, + validatePasswordError: nil, + comparePasswordsCount: 0, + comparePasswordsResponse: true, + createJWTTokenCount: 0, + expectedError: errors.New("user_not_in_organization"), + }, + { + comment: "can't use a password", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().PasswordLogin().Times(1).Return(false) + user.EXPECT().Activated().Times(0).Return(true) + user.EXPECT().Password().Times(0).Return("hashed password") + user.EXPECT().SetPassword("").Times(0) + user.EXPECT().Id().Times(0).Return(34) + user.EXPECT().Email().Times(0).Return("user@example.info") + user.EXPECT().SetToken("token").Times(0) + user.EXPECT().Organizations().Times(0).Return([]int{3, 454, 534}) + + return user + }, + validatePasswordCount: 0, + validatePasswordError: nil, + comparePasswordsCount: 0, + comparePasswordsResponse: true, + createJWTTokenCount: 0, + expectedError: errors.New("user_password_login_forbidden"), + }, + { + comment: "not activated", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().PasswordLogin().Times(1).Return(true) + user.EXPECT().Activated().Times(1).Return(false) + user.EXPECT().Password().Times(0).Return("hashed password") + user.EXPECT().SetPassword("").Times(0) + user.EXPECT().Id().Times(0).Return(34) + user.EXPECT().Email().Times(0).Return("user@example.info") + user.EXPECT().SetToken("token").Times(0) + user.EXPECT().Organizations().Times(0).Return([]int{3, 454, 534}) + + return user + }, + validatePasswordCount: 0, + validatePasswordError: nil, + comparePasswordsCount: 0, + comparePasswordsResponse: true, + createJWTTokenCount: 0, + expectedError: errors.New("user_not_activated"), + }, + { + comment: "non valid password", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().PasswordLogin().Times(1).Return(true) + user.EXPECT().Activated().Times(1).Return(true) + user.EXPECT().Password().Times(0).Return("hashed password") + user.EXPECT().SetPassword("").Times(0) + user.EXPECT().Id().Times(0).Return(34) + user.EXPECT().Email().Times(0).Return("user@example.info") + user.EXPECT().SetToken("token").Times(0) + user.EXPECT().Organizations().Times(1).Return([]int{3, 454, 534}) + return user + }, + validatePasswordCount: 1, + validatePasswordError: errors.New("bad password"), + comparePasswordsCount: 0, + comparePasswordsResponse: true, + createJWTTokenCount: 0, + expectedError: errors.New("login_invalid_password"), + }, + { + comment: "pass words don't match", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().PasswordLogin().Times(1).Return(true) + user.EXPECT().Activated().Times(1).Return(true) + user.EXPECT().Password().Times(1).Return("hashed password") + user.EXPECT().SetPassword("").Times(1) + user.EXPECT().Id().Times(0).Return(34) + user.EXPECT().Email().Times(0).Return("user@example.info") + user.EXPECT().SetToken("token").Times(0) + user.EXPECT().Organizations().Times(1).Return([]int{3, 454, 534}) + return user + }, + validatePasswordCount: 1, + validatePasswordError: nil, + comparePasswordsCount: 1, + comparePasswordsResponse: false, + createJWTTokenCount: 0, + expectedError: errors.New("login_invalid_password"), + }, + } + + for _, testCase := range testCases { + // arrange + user := testCase.getUserUser() + + mockUserRepo := mocks.NewMockUserRepo(mockCtrl) + mockPasswordResetRepo := mocks.NewMockResetRepo(mockCtrl) + + mockPasswordHelper := mocks.NewMockHelper(mockCtrl) + mockPasswordHelper.EXPECT().ValidatePassword("new password").Times(testCase.validatePasswordCount).Return(testCase.validatePasswordError) + mockPasswordHelper.EXPECT().ComparePasswords("hashed password", "new password").Times(testCase.comparePasswordsCount).Return(testCase.comparePasswordsResponse) + mockPasswordHelper.EXPECT().CreateJWTToken(34, 454, "user@example.info").Times(testCase.createJWTTokenCount).Return("token") + + basicHelper := users.NewUserHelper(mockUserRepo, mockPasswordResetRepo, mockPasswordHelper) + + // act + returnUser, err := basicHelper.Login("new password", 454, user) + + // assert + if testCase.expectedError == nil { + assert.Equal(t, user, returnUser) + } else { + assert.Nil(t, returnUser) + assert.Equal(t, testCase.expectedError, err) + } + } +} + +func TestBasicHelper_PasswordChangeForced(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + comment string + userId int + email string + newPassword string + getUserUser func() users.User + getUserError error + validatePasswordCount int + validatePasswordError error + hashPasswordCount int + hashPasswordResponse string + updateUserCount int + updateUserError error + expectedError error + }{ + { + comment: "working", + userId: 34, + email: "user@example.info", + newPassword: "new password", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Email().Times(1).Return("user@example.info") + user.EXPECT().SetPassword("hashed password").Times(1) + return user + }, + getUserError: nil, + validatePasswordCount: 1, + validatePasswordError: nil, + hashPasswordCount: 1, + hashPasswordResponse: "hashed password", + updateUserCount: 1, + updateUserError: nil, + expectedError: nil, + }, + { + comment: "weird email", + userId: 34, + email: " uSeR@example.INfo ", + newPassword: "new password", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Email().Times(1).Return("user@example.info") + user.EXPECT().SetPassword("hashed password").Times(1) + return user + }, + getUserError: nil, + validatePasswordCount: 1, + validatePasswordError: nil, + hashPasswordCount: 1, + hashPasswordResponse: "hashed password", + updateUserCount: 1, + updateUserError: nil, + expectedError: nil, + }, + { + comment: "can't find user", + userId: 34, + email: " uSeR@example.INfo ", + newPassword: "new password", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Email().Times(0).Return("user@example.info") + user.EXPECT().SetPassword("hashed password").Times(0) + return user + }, + getUserError: errors.New("can't find user"), + validatePasswordCount: 0, + validatePasswordError: nil, + hashPasswordCount: 0, + hashPasswordResponse: "hashed password", + updateUserCount: 0, + updateUserError: nil, + expectedError: errors.New("can't find user"), + }, + { + comment: "emails don't match", + userId: 34, + email: "user@example.infoo", + newPassword: "new password", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Email().Times(1).Return("user@example.info") + user.EXPECT().SetPassword("hashed password").Times(0) + return user + }, + getUserError: nil, + validatePasswordCount: 0, + validatePasswordError: nil, + hashPasswordCount: 0, + hashPasswordResponse: "hashed password", + updateUserCount: 0, + updateUserError: nil, + expectedError: errors.New("password_change_forbidden"), + }, + { + comment: "non valid passsword", + userId: 34, + email: "user@example.info", + newPassword: "new password", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Email().Times(1).Return("user@example.info") + user.EXPECT().SetPassword("hashed password").Times(0) + return user + }, + getUserError: nil, + validatePasswordCount: 1, + validatePasswordError: errors.New("non valid password"), + hashPasswordCount: 0, + hashPasswordResponse: "hashed password", + updateUserCount: 0, + updateUserError: nil, + expectedError: errors.New("non valid password"), + }, + { + comment: "can't update user", + userId: 34, + email: "user@example.info", + newPassword: "new password", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Email().Times(1).Return("user@example.info") + user.EXPECT().SetPassword("hashed password").Times(1) + return user + }, + getUserError: nil, + validatePasswordCount: 1, + validatePasswordError: nil, + hashPasswordCount: 1, + hashPasswordResponse: "hashed password", + updateUserCount: 1, + updateUserError: errors.New("can't update user"), + expectedError: errors.New("can't update user"), + }, + } + + for _, testCase := range testCases { + // arrange + user := testCase.getUserUser() + + mockUserRepo := mocks.NewMockUserRepo(mockCtrl) + mockUserRepo.EXPECT().GetUser(testCase.userId).Times(1).Return(user, testCase.getUserError) + mockUserRepo.EXPECT().UpdateUser(user).Times(testCase.updateUserCount).Return(nil, testCase.updateUserError) + + mockPasswordResetRepo := mocks.NewMockResetRepo(mockCtrl) + + mockPasswordHelper := mocks.NewMockHelper(mockCtrl) + mockPasswordHelper.EXPECT().ValidatePassword("new password").Times(testCase.validatePasswordCount).Return(testCase.validatePasswordError) + mockPasswordHelper.EXPECT().HashPassword("new password").Times(testCase.hashPasswordCount).Return("hashed password") + + basicHelper := users.NewUserHelper(mockUserRepo, mockPasswordResetRepo, mockPasswordHelper) + + // act + err := basicHelper.PasswordChangeForced(testCase.userId, testCase.email, testCase.newPassword) + + // assert + assert.Equal(t, testCase.expectedError, err) + } } diff --git a/users/basicUser.go b/users/basicUser.go index bcdcd15..6a09fa0 100644 --- a/users/basicUser.go +++ b/users/basicUser.go @@ -55,33 +55,14 @@ func (basic *BasicUser) PasswordLogin() bool { return basic.passwordlogin_ } -func (basic *BasicUser) AddOrganization(org int) { - if basic.Organizations_ == nil { - basic.Organizations_ = make([]int, 0) - } - basic.Organizations_ = append(basic.Organizations_, org) -} - -func (basic *BasicUser) RemoveOrganization(org int) { - if basic.Organizations_ == nil { - return - } - - updatedList := make([]int, 0) - for _, orgIdOld := range basic.Organizations_ { - if orgIdOld != org { - updatedList = append(updatedList, orgIdOld) - } - } - - basic.Organizations_ = updatedList - -} - func (basic *BasicUser) Organizations() []int { return basic.Organizations_ } +func (basic *BasicUser) SetOrganizations(organizations ...int) { + basic.Organizations_ = organizations +} + /** Provide code to copy the user into this user */ diff --git a/users/handlers.go b/users/handlers.go index a774671..bbd5173 100644 --- a/users/handlers.go +++ b/users/handlers.go @@ -162,7 +162,7 @@ func (handler *Handler) handleUserCreate(w http.ResponseWriter, r *http.Request) //Copy over the new user data newUser.SetEmail(newUserInfo.Email) newUser.SetPassword(newUserInfo.Password) - newUser.AddOrganization(newUserInfo.OrganizationId) + newUser.SetOrganizations(newUserInfo.OrganizationId) //Now create the new user err = handler.userHelper.CreateUser(newUser) diff --git a/users/handlers_test.go b/users/handlers_test.go index 586a365..0d647a5 100644 --- a/users/handlers_test.go +++ b/users/handlers_test.go @@ -49,7 +49,7 @@ func TestHandler_handleUserCreate(t *testing.T) { user := mocks.NewMockUser(mockCtrl) user.EXPECT().SetEmail("user@example.info").Times(1) user.EXPECT().SetPassword("new password").Times(1) - user.EXPECT().AddOrganization(34).Times(1) + user.EXPECT().SetOrganizations(34).Times(1) return user }, expectDecodeSuccessful: true, @@ -76,7 +76,7 @@ func TestHandler_handleUserCreate(t *testing.T) { user := mocks.NewMockUser(mockCtrl) user.EXPECT().SetEmail("user@example.info").Times(1) user.EXPECT().SetPassword("new password").Times(1) - user.EXPECT().AddOrganization(34).Times(1) + user.EXPECT().SetOrganizations(34).Times(1) return user }, expectDecodeSuccessful: true, diff --git a/users/Repo.go b/users/repo.go similarity index 92% rename from users/Repo.go rename to users/repo.go index 0b9b617..32d35c6 100644 --- a/users/Repo.go +++ b/users/repo.go @@ -39,6 +39,5 @@ type Repo interface { /** 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 54% rename from users/RepoSql.go rename to users/repoSql.go index f8e4a0b..e4449ce 100644 --- a/users/RepoSql.go +++ b/users/repoSql.go @@ -6,13 +6,17 @@ package users import ( "database/sql" "errors" - "log" "strings" "time" "github.com/reaction-eng/restlib/utils" ) +const UserTableName = "users" +const OrgTableName = "userOrgs" + +var UserNotFound = errors.New("login_email_not_found") + /** Define a struct for Repo for use with users */ @@ -23,162 +27,113 @@ type RepoSql struct { //Also store the table name tableName string - //Store the required statements to reduce comput time + //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 - - //Store the nullable time object - } //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, + db: db, } - //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) - } - - //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 //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, + db: db, } - //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) - } - - //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") + getUserByEmail, err := db.Prepare("SELECT * FROM " + UserTableName + " where email like $1") //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 = $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 - + return &newRepo, nil } /** @@ -199,15 +154,13 @@ 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, err + return nil, UserNotFound } //Store if this is activated user.activated_ = activationDate.Valid user.passwordlogin_ = len(user.password_) > 0 - //Return the user calcs return &user, err } @@ -215,7 +168,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 @@ -233,53 +185,20 @@ func (repo *RepoSql) GetUser(id int) (User, error) { 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) { +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) - } - 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) - - } - err = rows.Err() - - return list, err -} - -/** -List all of the users -*/ -func (repo *RepoSql) ListAllActiveUsers() ([]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() { @@ -293,7 +212,7 @@ func (repo *RepoSql) ListAllActiveUsers() ([]int, error) { } //Append the row - if activationDate.Valid { + if !onlyActive || activationDate.Valid { list = append(list, id) } } @@ -302,12 +221,15 @@ func (repo *RepoSql) ListAllActiveUsers() ([]int, error) { return list, err } -/** -Add the user to the database -*/ func (repo *RepoSql) AddUser(newUser User) (User, error) { - //TODO: make sure user is not already here + _, 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) @@ -315,7 +237,7 @@ func (repo *RepoSql) AddUser(newUser User) (User, error) { //Check for error if err != nil { - return newUser, err + return nil, err } //Now look up the person by email @@ -323,26 +245,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{ @@ -352,18 +261,9 @@ 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()) - - //Check for error - if err != nil { - log.Fatal(err) - } - return err } -/** -Clean up the database, nothing much to do -*/ func (repo *RepoSql) CleanUp() { repo.addUserStatement.Close() repo.getUserByEmailStatement.Close() @@ -372,19 +272,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..c8382d1 --- /dev/null +++ b/users/repoSql_test.go @@ -0,0 +1,775 @@ +// 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) + + 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) + + 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) + + return db, mock +} + +func TestResetRepoSql_GetUserByEmail(t *testing.T) { + testCases := []struct { + comment string + emailInput string + expectedError error + query 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())) + }, + 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())) + }, + userId: 43, + userOrgs: []int{54, 23}, + userEmail: "user@example.com", + userToken: "", + userActivated: true, + userPasswordLogin: true, + }, + { + comment: "no rows", + emailInput: "user@example.com", + expectedError: errors.New("login_email_not_found"), + query: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs("user@example.com").WillReturnError(sql.ErrNoRows) + }, + }, + { + 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)) + }, + 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())) + }, + 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)) + }, + userId: 43, + userOrgs: []int{54, 23}, + userEmail: "user@example.com", + userToken: "", + userActivated: false, + userPasswordLogin: false, + }, + } + + for _, testCase := range testCases { + // arrange + mockCtrl := gomock.NewController(t) + + db, dbMock := setupSqlMock(t) + + repo, _ := users.NewRepoPostgresSql(db) + testCase.query(dbMock.ExpectQuery("SELECT \\* FROM users")) + + // act + user, err := repo.GetUserByEmail(testCase.emailInput) + + // assert + assert.Equal(t, testCase.expectedError, err) + if err == nil { + assert.Equal(t, testCase.userId, user.Id()) + //TODO: 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) + 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())) + }, + userId: 43, + userOrgs: []int{54, 23}, + userEmail: "user@example.com", + userToken: "", + userActivated: true, + userPasswordLogin: true, + }, + { + comment: "no rows", + idInput: 43, + expectedError: errors.New("login_user_id_not_found"), + query: func(query *sqlmock.ExpectedQuery) { + query. + WithArgs(43).WillReturnError(sql.ErrNoRows) + }, + }, + { + 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)) + }, + userId: 43, + userOrgs: []int{54, 23}, + 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())) + }, + 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)) + }, + userId: 43, + userOrgs: []int{54, 23}, + userEmail: "user@example.com", + userToken: "", + userActivated: false, + userPasswordLogin: false, + }, + } + + for _, testCase := range testCases { + // arrange + mockCtrl := gomock.NewController(t) + + db, dbMock := setupSqlMock(t) + + repo, _ := users.NewRepoPostgresSql(db) + testCase.query(dbMock.ExpectQuery("SELECT \\* FROM users")) + + // act + user, err := repo.GetUser(testCase.idInput) + + // assert + assert.Equal(t, testCase.expectedError, err) + if err == nil { + assert.Equal(t, testCase.userId, user.Id()) + //TODO: 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) + addUserStatementExec func(exec *sqlmock.ExpectedExec) + getUserByEmailQuery func(query *sqlmock.ExpectedQuery) + userId int + userOrgs []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)) + }, + userId: 43, + userOrgs: []int{54, 23}, + 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)) + }, + addUserStatementExec: func(exec *sqlmock.ExpectedExec) { + + }, + getUserByEmailQuery: 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").WillReturnError(users.UserNotFound) + }, + 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) { + + }, + }, + { + 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")) + }, + }, + } + + for _, testCase := range testCases { + // arrange + db, dbMock := setupSqlMock(t) + + repo, _ := users.NewRepoPostgresSql(db) + testCase.checkUserStatementExec(dbMock.ExpectQuery("SELECT \\* FROM users ")) + testCase.addUserStatementExec(dbMock.ExpectExec("INSERT INTO users ")) + testCase.getUserByEmailQuery(dbMock.ExpectQuery("SELECT \\* FROM users ")) + + // act + user, err := repo.AddUser(testCase.inputUser()) + + // assert + assert.Equal(t, testCase.expectedError, err) + if err == nil { + assert.Equal(t, testCase.userId, user.Id()) + //TODO: 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() + } +} + +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() + } +} From a14955727a7aa32754a3737dab61cf0d1c58c802 Mon Sep 17 00:00:00 2001 From: mcgurn Date: Sun, 17 May 2020 14:15:21 -0600 Subject: [PATCH 15/23] unit tests for users and added org into user repo --- go.mod | 4 - migrations/mysql/3_initial_users.sql | 4 +- migrations/postgres/3_initial_users.sql | 4 +- mocks/mock_resetRepo.go | 29 ++ mocks/mock_user_helper.go | 62 ++- mocks/mock_users_repo.go | 33 +- passwords/resetRepo.go | 6 +- passwords/resetRepoSql.go | 110 ++++- passwords/resetRepoSql_test.go | 173 ++++++- users/{Documentation.go => documentation.go} | 0 users/{Google.go => google.go} | 0 users/handlers.go | 1 - users/oneTimePassword.go | 184 ++++++++ users/oneTimePassword_test.go | 468 +++++++++++++++++++ users/repo.go | 8 + users/repoSql.go | 121 ++++- users/repoSql_test.go | 461 +++++++++++++++++- 17 files changed, 1613 insertions(+), 55 deletions(-) rename users/{Documentation.go => documentation.go} (100%) rename users/{Google.go => google.go} (100%) create mode 100644 users/oneTimePassword.go create mode 100644 users/oneTimePassword_test.go diff --git a/go.mod b/go.mod index 40373a0..a21cfd4 100644 --- a/go.mod +++ b/go.mod @@ -13,11 +13,9 @@ require ( 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/go-stack/stack v1.8.0 // indirect 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/golang/snappy v0.0.1 // indirect github.com/gorilla/mux v1.7.4 github.com/gorilla/websocket v1.4.2 // indirect github.com/klauspost/compress v1.10.5 // indirect @@ -30,9 +28,7 @@ require ( 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/tidwall/pretty v1.0.0 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect - github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect github.com/xdg/stringprep v1.0.0 // indirect go.mongodb.org/mongo-driver v1.3.2 golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 diff --git a/migrations/mysql/3_initial_users.sql b/migrations/mysql/3_initial_users.sql index 9ced578..584b846 100644 --- a/migrations/mysql/3_initial_users.sql +++ b/migrations/mysql/3_initial_users.sql @@ -1,5 +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; \ No newline at end of file +DROP TABLE users; +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 index 72e873a..e162303 100644 --- a/migrations/postgres/3_initial_users.sql +++ b/migrations/postgres/3_initial_users.sql @@ -1,5 +1,7 @@ -- +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 userpref (userId int NOT NULL, orgId int NOT NULL, joinDate Date) -- +migrate Down -DROP TABLE users; \ No newline at end of file +DROP TABLE users; +DROP TABLE userpref; \ No newline at end of file diff --git a/mocks/mock_resetRepo.go b/mocks/mock_resetRepo.go index 866e280..a3dbfa0 100644 --- a/mocks/mock_resetRepo.go +++ b/mocks/mock_resetRepo.go @@ -47,6 +47,21 @@ func (mr *MockResetRepoMockRecorder) CheckForActivationToken(arg0, arg1 interfac 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() @@ -88,6 +103,20 @@ func (mr *MockResetRepoMockRecorder) IssueActivationRequest(arg0, arg1, arg2 int 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() diff --git a/mocks/mock_user_helper.go b/mocks/mock_user_helper.go index 4ab1cf5..e2b73ae 100644 --- a/mocks/mock_user_helper.go +++ b/mocks/mock_user_helper.go @@ -5,10 +5,9 @@ package mocks import ( - reflect "reflect" - gomock "github.com/golang/mock/gomock" users "github.com/reaction-eng/restlib/users" + reflect "reflect" ) // MockUserHelper is a mock of Helper interface @@ -63,6 +62,20 @@ func (mr *MockUserHelperMockRecorder) AddUser(arg0 interface{}) *gomock.Call { 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() @@ -78,6 +91,21 @@ func (mr *MockUserHelperMockRecorder) CheckForActivationToken(arg0, arg1 interfa 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() @@ -205,6 +233,20 @@ func (mr *MockUserHelperMockRecorder) IssueActivationRequest(arg0, arg1, arg2 in 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() @@ -229,7 +271,7 @@ func (m *MockUserHelper) ListUsers(arg0 bool, arg1 []int) ([]int, error) { } // ListUsers indicates an expected call of ListUsers -func (mr *MockUserHelperMockRecorder) ListAllUsers(arg0, arg1 interface{}) *gomock.Call { +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) } @@ -291,6 +333,20 @@ func (mr *MockUserHelperMockRecorder) PasswordChangeForced(arg0, arg1, arg2 inte 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() diff --git a/mocks/mock_users_repo.go b/mocks/mock_users_repo.go index a85c693..00cd6dd 100644 --- a/mocks/mock_users_repo.go +++ b/mocks/mock_users_repo.go @@ -5,10 +5,9 @@ package mocks import ( - reflect "reflect" - gomock "github.com/golang/mock/gomock" users "github.com/reaction-eng/restlib/users" + reflect "reflect" ) // MockUserRepo is a mock of Repo interface @@ -63,6 +62,20 @@ func (mr *MockUserRepoMockRecorder) AddUser(arg0 interface{}) *gomock.Call { 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() @@ -103,7 +116,7 @@ func (m *MockUserRepo) ListUsers(arg0 bool, arg1 []int) ([]int, error) { } // ListUsers indicates an expected call of ListUsers -func (mr *MockUserRepoMockRecorder) ListAllUsers(arg0, arg1 interface{}) *gomock.Call { +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) } @@ -122,6 +135,20 @@ func (mr *MockUserRepoMockRecorder) NewEmptyUser() *gomock.Call { 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() diff --git a/passwords/resetRepo.go b/passwords/resetRepo.go index 55cfe57..674e3df 100644 --- a/passwords/resetRepo.go +++ b/passwords/resetRepo.go @@ -8,12 +8,16 @@ package passwords type ResetRepo interface { IssueResetRequest(token string, userId int, email string) error - CheckForResetToken(userId int, reset_token string) (int, error) + CheckForResetToken(userId int, resetToken string) (int, error) IssueActivationRequest(token string, userId int, email string) error CheckForActivationToken(userId int, activationToken string) (int, error) + IssueOneTimePasswordRequest(token string, userId int, email string) error + + CheckForOneTimePasswordToken(userId int, activationToken string) (int, error) + UseToken(id int) error CleanUp() diff --git a/passwords/resetRepoSql.go b/passwords/resetRepoSql.go index 8d68bbb..ee53f2d 100644 --- a/passwords/resetRepoSql.go +++ b/passwords/resetRepoSql.go @@ -14,19 +14,24 @@ import ( const TableName = "resetrequests" +var TokenExpired = errors.New("token_expired") + type ResetRepoSql struct { //Hold on to the sql databased db *sql.DB //We need the emailer - emailer email.Emailer - resetEmailConfig PasswordResetConfig - activationEmailConfig PasswordResetConfig + emailer email.Emailer + resetEmailConfig PasswordResetConfig + activationEmailConfig PasswordResetConfig + oneTimePasswordEmailConfig PasswordResetConfig //Store the required statements to reduce compute time addRequestStatement *sql.Stmt getRequestStatement *sql.Stmt rmRequestStatement *sql.Stmt + + tokenLifeSpan float64 } /** @@ -35,8 +40,9 @@ Store the type of token type tokenType int const ( - activation tokenType = 1 - reset tokenType = 2 + activation tokenType = 1 + reset tokenType = 2 + oneTimePassword tokenType = 3 ) func NewRepoMySql(db *sql.DB, emailer email.Emailer, configuration configuration.Configuration) (*ResetRepoSql, error) { @@ -44,10 +50,25 @@ func NewRepoMySql(db *sql.DB, emailer email.Emailer, configuration configuration //Build a reset and activation config resetEmailConfig := PasswordResetConfig{} activationEmailConfig := PasswordResetConfig{} + oneTimePasswordEmailConfig := PasswordResetConfig{} //Pull from the config - configuration.GetStruct("password_reset", &resetEmailConfig) + 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{ @@ -55,6 +76,7 @@ func NewRepoMySql(db *sql.DB, emailer email.Emailer, configuration configuration emailer: emailer, resetEmailConfig: resetEmailConfig, activationEmailConfig: activationEmailConfig, + tokenLifeSpan: tokenLifeSpan, } //Add request data to table @@ -93,10 +115,25 @@ func NewRepoPostgresSql(db *sql.DB, emailer email.Emailer, configuration configu //Build a reset and activation config resetEmailConfig := PasswordResetConfig{} activationEmailConfig := PasswordResetConfig{} + oneTimePasswordEmailConfig := PasswordResetConfig{} //Pull from the config - configuration.GetStruct("password_reset", &resetEmailConfig) + 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{ @@ -104,6 +141,7 @@ func NewRepoPostgresSql(db *sql.DB, emailer email.Emailer, configuration configu emailer: emailer, resetEmailConfig: resetEmailConfig, activationEmailConfig: activationEmailConfig, + tokenLifeSpan: tokenLifeSpan, } //Add request data to table @@ -192,16 +230,39 @@ func (repo *ResetRepoSql) IssueActivationRequest(token string, userId int, email return err } -/** -Use the token to validate -*/ +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.activationEmailConfig.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 err +} + 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") } @@ -209,16 +270,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") } @@ -226,6 +284,20 @@ func (repo *ResetRepoSql) CheckForActivationToken(userId int, token string) (int } +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 @@ -240,14 +312,16 @@ func (repo *ResetRepoSql) checkForToken(userId int, token string, tkType tokenTy //Get the value err := repo.getRequestStatement.QueryRow(userId, token, tkType).Scan(&id, &userIdDb, &emailDb, &tokenDb, &issued, &tokenType) - //So it was correct, check the date - //TODO: check the date - //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 { return -1, errors.New("invalid_token") diff --git a/passwords/resetRepoSql_test.go b/passwords/resetRepoSql_test.go index 3f6f508..c45c2a0 100644 --- a/passwords/resetRepoSql_test.go +++ b/passwords/resetRepoSql_test.go @@ -48,6 +48,12 @@ func TestNewRepoMySql(t *testing.T) { 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) @@ -84,6 +90,12 @@ func TestNewRepoPostgresSql(t *testing.T) { 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) @@ -115,6 +127,12 @@ func setupSqlMock(t *testing.T, mockCtrl *gomock.Controller, tableName string) ( 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 } @@ -305,6 +323,7 @@ func TestResetRepoSql_CheckForResetToken(t *testing.T) { token string userIdDb int tokenDb string + issueTime time.Time queryError error rowId int expectedRowId int @@ -315,6 +334,18 @@ func TestResetRepoSql_CheckForResetToken(t *testing.T) { "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, @@ -325,6 +356,7 @@ func TestResetRepoSql_CheckForResetToken(t *testing.T) { "example token", 100, "other token token", + time.Now(), nil, 1023, -1, @@ -335,6 +367,7 @@ func TestResetRepoSql_CheckForResetToken(t *testing.T) { "example token", 102, "other example token", + time.Now(), nil, 1023, -1, @@ -345,6 +378,7 @@ func TestResetRepoSql_CheckForResetToken(t *testing.T) { "example token", 102, "example token", + time.Now(), nil, 1023, -1, @@ -355,6 +389,7 @@ func TestResetRepoSql_CheckForResetToken(t *testing.T) { "example token", 100, "example token", + time.Now(), errors.New("queryError"), 1023, -1, @@ -371,7 +406,7 @@ func TestResetRepoSql_CheckForResetToken(t *testing.T) { 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, time.Now(), 1) + AddRow(testCase.rowId, testCase.userIdDb, "email", testCase.tokenDb, testCase.issueTime, 1) dbMock.ExpectQuery("SELECT (.+) FROM " + passwords.TableName). WillReturnRows(rows). @@ -399,6 +434,7 @@ func TestResetRepoSql_CheckForActivationToken(t *testing.T) { token string userIdDb int tokenDb string + issueTime time.Time queryError error rowId int expectedRowId int @@ -409,6 +445,18 @@ func TestResetRepoSql_CheckForActivationToken(t *testing.T) { "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, @@ -419,6 +467,7 @@ func TestResetRepoSql_CheckForActivationToken(t *testing.T) { "example token", 100, "other token token", + time.Now(), nil, 1023, -1, @@ -429,6 +478,7 @@ func TestResetRepoSql_CheckForActivationToken(t *testing.T) { "example token", 102, "other example token", + time.Now(), nil, 1023, -1, @@ -439,6 +489,7 @@ func TestResetRepoSql_CheckForActivationToken(t *testing.T) { "example token", 102, "example token", + time.Now(), nil, 1023, -1, @@ -449,6 +500,7 @@ func TestResetRepoSql_CheckForActivationToken(t *testing.T) { "example token", 100, "example token", + time.Now(), errors.New("queryError"), 1023, -1, @@ -465,7 +517,7 @@ func TestResetRepoSql_CheckForActivationToken(t *testing.T) { 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, time.Now(), 2) + AddRow(testCase.rowId, testCase.userIdDb, "email", testCase.tokenDb, testCase.issueTime, 2) dbMock.ExpectQuery("SELECT (.+) FROM " + passwords.TableName). WillReturnRows(rows). @@ -487,6 +539,117 @@ func TestResetRepoSql_CheckForActivationToken(t *testing.T) { } } +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 @@ -555,6 +718,12 @@ func TestResetRepoSql_CleanUp(t *testing.T) { 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) diff --git a/users/Documentation.go b/users/documentation.go similarity index 100% rename from users/Documentation.go rename to users/documentation.go diff --git a/users/Google.go b/users/google.go similarity index 100% rename from users/Google.go rename to users/google.go diff --git a/users/handlers.go b/users/handlers.go index bbd5173..24fa5ac 100644 --- a/users/handlers.go +++ b/users/handlers.go @@ -519,5 +519,4 @@ func (handler *Handler) handleUserActivationGet(w http.ResponseWriter, r *http.R //Now just return utils.ReturnJsonStatus(w, http.StatusOK, true, "activation_token_request_received") - } diff --git a/users/oneTimePassword.go b/users/oneTimePassword.go new file mode 100644 index 0000000..1f0eb33 --- /dev/null +++ b/users/oneTimePassword.go @@ -0,0 +1,184 @@ +// 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 + +import ( + "encoding/json" + "net/http" + "strconv" + + "github.com/reaction-eng/restlib/routing" + "github.com/reaction-eng/restlib/utils" +) + +type OneTimePasswordHandler struct { + // The user handler needs to have access to user repo + helper Helper +} + +func NewOneTimePasswordHandler(helper Helper) *OneTimePasswordHandler { + //Create a new + oneTimePasswordHandler := &OneTimePasswordHandler{ + helper: helper, + } + + return oneTimePasswordHandler +} + +func (handler *OneTimePasswordHandler) GetRoutes() []routing.Route { + + var routes = []routing.Route{ + { + Name: "Get OneTimePassword Token", + Method: "GET", + Pattern: "/users/onetimelogin", + HandlerFunc: handler.handleOneTimePasswordGet, + Public: true, + }, + { + Name: "Login With OneTimePassword Token", + Method: "POST", + Pattern: "/users/onetimelogin", + HandlerFunc: handler.handleOneTimePasswordLoginPut, + Public: true, + }, + } + + return routes + +} + +func (handler *OneTimePasswordHandler) handleOneTimePasswordGet(w http.ResponseWriter, r *http.Request) { + + //Now get the email that was passed in + emailKeys, ok := r.URL.Query()["email"] + + //Only take the first one + if !ok || len(emailKeys[0]) < 1 { + utils.ReturnJsonStatus(w, http.StatusUnprocessableEntity, false, "onetimepassword_token_missing_email") + return + } + + orgKeys, ok := r.URL.Query()["organizationId"] + if !ok || len(emailKeys[0]) < 1 { + utils.ReturnJsonStatus(w, http.StatusUnprocessableEntity, false, "onetimepassword_token_missing_organizationId") + return + } + + //Get the email + email := emailKeys[0] + organizationIdString := orgKeys[0] + organizationId, err := strconv.Atoi(organizationIdString) + if err != nil { + utils.ReturnJsonStatus(w, http.StatusUnprocessableEntity, false, "onetimepassword_token_missing_organizationId") + return + } + + //Look up the user + user, err := handler.helper.GetUserByEmail(email) + + //If there is no user create them + if err == UserNotFound { + //The email is not in use, so add it + //Create an empty new user + newUser := handler.helper.NewEmptyUser() + newUser.SetEmail(email) + newUser.SetPassword("") //This is a blank password that prevents being able to login + newUser.SetOrganizations(organizationId) + + //Now store it + user, err = handler.helper.AddUser(newUser) + + //Make sure it created an id + if err != nil { + utils.ReturnJsonError(w, http.StatusForbidden, err) + return + } + } else if err != nil { + utils.ReturnJsonError(w, http.StatusServiceUnavailable, err) + return + } + + err = handler.helper.IssueOneTimePasswordRequest(handler.helper.TokenGenerator(), user.Id(), user.Email()) + + if err != nil { + utils.ReturnJsonError(w, http.StatusServiceUnavailable, err) + return + } + + //Now just return + utils.ReturnJsonStatus(w, http.StatusOK, true, "onetimepassword_token_request_received") +} + +func (handler *OneTimePasswordHandler) handleOneTimePasswordLoginPut(w http.ResponseWriter, r *http.Request) { + + //Define a local struct to get the email out of the request + type LoginGet struct { + Email string `json:"email"` + LoginToken string `json:"login_token"` + OrganizationId int `json:"organizationId` + } + + info := LoginGet{} + + //Now get the json info + err := json.NewDecoder(r.Body).Decode(&info) + if err != nil { + utils.ReturnJsonError(w, http.StatusUnprocessableEntity, err) + return + } + + //Lookup the user id + user, err := handler.helper.GetUserByEmail(info.Email) + + //Return the error + if err == UserNotFound { + utils.ReturnJsonStatus(w, http.StatusForbidden, false, "onetimepassword_forbidden") + return + } else if err != nil { + utils.ReturnJsonStatus(w, http.StatusServiceUnavailable, false, "onetimepassword_forbidden") + return + } + + if !inList(user.Organizations(), info.OrganizationId) { + utils.ReturnJsonStatus(w, http.StatusForbidden, false, UserNotInOrganization.Error()) + return + } + + //Try to use the token + requestId, err := handler.helper.CheckForOneTimePasswordToken(user.Id(), info.LoginToken) + + //Return the error + if err != nil { + utils.ReturnJsonStatus(w, http.StatusForbidden, false, "onetimepassword_forbidden") + return + } + + err = handler.helper.UseToken(requestId) + if err != nil { + //There prob is not a user to return + utils.ReturnJsonError(w, http.StatusForbidden, err) + return + } + + //If the user was not activated, activate them + if !user.Activated() { + err = handler.helper.ActivateUser(user) + if err != nil { + utils.ReturnJsonStatus(w, http.StatusServiceUnavailable, false, "onetimepassword_forbidden") + return + } + + user, err = handler.helper.GetUser(user.Id()) + if err != nil { + utils.ReturnJsonStatus(w, http.StatusServiceUnavailable, false, "onetimepassword_forbidden") + return + } + } + + //Create JWT token and Store the token in the response + user.SetToken(handler.helper.CreateJWTToken(user.Id(), info.OrganizationId, user.Email())) + + utils.ReturnJson(w, http.StatusCreated, user) +} diff --git a/users/oneTimePassword_test.go b/users/oneTimePassword_test.go new file mode 100644 index 0000000..b31417b --- /dev/null +++ b/users/oneTimePassword_test.go @@ -0,0 +1,468 @@ +// 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 ( + "errors" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/golang/mock/gomock" + "github.com/reaction-eng/restlib/mocks" + "github.com/reaction-eng/restlib/users" + "github.com/stretchr/testify/assert" +) + +func TestHandler_handleOneTimePasswordGet(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + testCases := []struct { + comment string + url string + getUserUser func() users.User + newUserUser func() users.User + getUserByEmailCount int + getUserByEmailError error + issueOneTimePasswordRequestCount int + issueOneTimePasswordRequestError error + addUserCount int + addUserError error + tokenGeneratorCount int + expectedStatus int + expectedResponse string + }{ + { + comment: "working", + url: "http://localhost/users/onetimelogin?email=user@example.com&organizationId=43", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(1).Return(34) + user.EXPECT().Email().Times(1).Return("user@example.com") + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: nil, + issueOneTimePasswordRequestCount: 1, + issueOneTimePasswordRequestError: nil, + newUserUser: func() users.User { + return nil + }, + addUserCount: 0, + addUserError: nil, + tokenGeneratorCount: 1, + expectedStatus: http.StatusOK, + expectedResponse: "{\"message\":\"onetimepassword_token_request_received\",\"status\":true}\n", + }, + { + comment: "user must specify email", + url: "http://localhost/users/onetimelogin?&organizationId=43", + getUserUser: func() users.User { + return nil + }, + getUserByEmailCount: 0, + getUserByEmailError: nil, + issueOneTimePasswordRequestCount: 0, + issueOneTimePasswordRequestError: nil, + newUserUser: func() users.User { + return nil + }, + addUserCount: 0, + addUserError: nil, + tokenGeneratorCount: 0, + expectedStatus: http.StatusUnprocessableEntity, + expectedResponse: "{\"message\":\"onetimepassword_token_missing_email\",\"status\":false}\n", + }, + { + comment: "user must specify organizationId", + url: "http://localhost/users/onetimelogin?&email=user@example.com", + getUserUser: func() users.User { + return nil + }, + getUserByEmailCount: 0, + getUserByEmailError: nil, + issueOneTimePasswordRequestCount: 0, + issueOneTimePasswordRequestError: nil, + newUserUser: func() users.User { + return nil + }, + addUserCount: 0, + addUserError: nil, + tokenGeneratorCount: 0, + expectedStatus: http.StatusUnprocessableEntity, + expectedResponse: "{\"message\":\"onetimepassword_token_missing_organizationId\",\"status\":false}\n", + }, + { + comment: "missing user should create a new user", + url: "http://localhost/users/onetimelogin?email=user@example.com&organizationId=43", + getUserUser: func() users.User { + return nil + }, + getUserByEmailCount: 1, + getUserByEmailError: users.UserNotFound, + issueOneTimePasswordRequestCount: 1, + issueOneTimePasswordRequestError: nil, + newUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().SetEmail("user@example.com").Times(1) + user.EXPECT().SetPassword("").Times(1) + user.EXPECT().SetOrganizations(43).Times(1) + user.EXPECT().Id().Times(1).Return(34) + user.EXPECT().Email().Times(1).Return("user@example.com") + return user + }, + addUserCount: 1, + addUserError: nil, + tokenGeneratorCount: 1, + expectedStatus: http.StatusOK, + expectedResponse: "{\"message\":\"onetimepassword_token_request_received\",\"status\":true}\n", + }, + { + comment: "should error if add user db error", + url: "http://localhost/users/onetimelogin?email=user@example.com&organizationId=43", + getUserUser: func() users.User { + return nil + }, + getUserByEmailCount: 1, + getUserByEmailError: users.UserNotFound, + newUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().SetEmail("user@example.com").Times(1) + user.EXPECT().SetPassword("").Times(1) + user.EXPECT().SetOrganizations(43).Times(1) + return user + }, + addUserCount: 1, + addUserError: errors.New("db error"), + issueOneTimePasswordRequestCount: 0, + issueOneTimePasswordRequestError: nil, + tokenGeneratorCount: 0, + expectedStatus: http.StatusForbidden, + expectedResponse: "{\"message\":\"db error\",\"status\":false}\n", + }, + { + comment: "get user should error with other error", + url: "http://localhost/users/onetimelogin?email=user@example.com&organizationId=43", + getUserUser: func() users.User { + return nil + }, + getUserByEmailCount: 1, + getUserByEmailError: errors.New("other error"), + issueOneTimePasswordRequestCount: 0, + issueOneTimePasswordRequestError: nil, + newUserUser: func() users.User { + return nil + }, + addUserCount: 0, + addUserError: nil, + tokenGeneratorCount: 0, + expectedStatus: http.StatusServiceUnavailable, + expectedResponse: "{\"message\":\"other error\",\"status\":false}\n", + }, + { + comment: "should return error is can't issue password ", + url: "http://localhost/users/onetimelogin?email=user@example.com&organizationId=43", + getUserUser: func() users.User { + user := mocks.NewMockUser(mockCtrl) + user.EXPECT().Id().Times(1).Return(34) + user.EXPECT().Email().Times(1).Return("user@example.com") + return user + }, + getUserByEmailCount: 1, + getUserByEmailError: nil, + newUserUser: func() users.User { + return nil + }, + addUserCount: 0, + addUserError: nil, + issueOneTimePasswordRequestCount: 1, + issueOneTimePasswordRequestError: errors.New("can't send email"), + tokenGeneratorCount: 1, + expectedStatus: http.StatusServiceUnavailable, + expectedResponse: "{\"message\":\"can't send email\",\"status\":false}\n", + }, + } + + // arrange + for _, testCase := range testCases { + mockHelper := mocks.NewMockUserHelper(mockCtrl) + + mockHelper.EXPECT().GetUserByEmail("user@example.com").Times(testCase.getUserByEmailCount).Return(testCase.getUserUser(), testCase.getUserByEmailError) + token := "token123" + + if testCase.addUserCount > 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) + + handler := users.NewOneTimePasswordHandler(mockHelper) + router := mocks.NewTestRouter(handler, nil) // 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, nil) // 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 index 32d35c6..ae899e3 100644 --- a/users/repo.go +++ b/users/repo.go @@ -3,8 +3,13 @@ package users +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 @@ -36,6 +41,9 @@ type Repo interface { */ NewEmptyUser() User + AddUserToOrganization(user User, orgId int) error + RemoveUserFromOrganization(user User, orgId int) error + /** List all users */ diff --git a/users/repoSql.go b/users/repoSql.go index e4449ce..ec4ef90 100644 --- a/users/repoSql.go +++ b/users/repoSql.go @@ -13,9 +13,7 @@ import ( ) const UserTableName = "users" -const OrgTableName = "userOrgs" - -var UserNotFound = errors.New("login_email_not_found") +const UserOrgTableName = "userOrganizations" /** Define a struct for Repo for use with users @@ -28,12 +26,15 @@ type RepoSql struct { tableName string //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 + 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 @@ -83,6 +84,24 @@ func NewRepoMySql(db *sql.DB) (*RepoSql, error) { } 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, nil @@ -109,7 +128,6 @@ func NewRepoPostgresSql(db *sql.DB) (*RepoSql, error) { newRepo.getUserStatement = getUser getUserByEmail, err := db.Prepare("SELECT * FROM " + UserTableName + " where email like $1") - //Check for error if err != nil { return nil, err } @@ -133,6 +151,24 @@ func NewRepoPostgresSql(db *sql.DB) (*RepoSql, error) { } newRepo.listAllUsersStatement = listAllUsers + 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 } @@ -155,12 +191,20 @@ func (repo *RepoSql) GetUserByEmail(email string) (User, error) { //Use a useful error if err == sql.ErrNoRows { 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 + orgIds, err := repo.listUserOrganizations(user.Id_) + if err != nil { + return nil, err + } + user.SetOrganizations(orgIds...) + return &user, err } @@ -178,13 +222,21 @@ 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 + orgIds, err := repo.listUserOrganizations(user.Id_) + if err != nil { + return nil, err + } + user.SetOrganizations(orgIds...) + return &user, err } @@ -264,6 +316,53 @@ func (repo *RepoSql) ActivateUser(user User) error { return err } +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 { + return err + } + if rowsChange < 1 { + return errors.New("no_organizations_removed") + } + + return err +} + +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() diff --git a/users/repoSql_test.go b/users/repoSql_test.go index c8382d1..bb4c363 100644 --- a/users/repoSql_test.go +++ b/users/repoSql_test.go @@ -31,6 +31,9 @@ func TestNewRepoMySql(t *testing.T) { 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() @@ -57,6 +60,9 @@ func TestNewRepoPostgresSql(t *testing.T) { 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() @@ -81,6 +87,9 @@ func setupSqlMock(t *testing.T) (*sql.DB, sqlmock.Sqlmock) { 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 } @@ -91,6 +100,7 @@ func TestResetRepoSql_GetUserByEmail(t *testing.T) { emailInput string expectedError error query func(*sqlmock.ExpectedQuery) + queryOrgs func(*sqlmock.ExpectedQuery) userId int userOrgs []int userEmail string @@ -110,6 +120,14 @@ func TestResetRepoSql_GetUserByEmail(t *testing.T) { []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", @@ -129,6 +147,14 @@ func TestResetRepoSql_GetUserByEmail(t *testing.T) { []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", @@ -139,11 +165,19 @@ func TestResetRepoSql_GetUserByEmail(t *testing.T) { { comment: "no rows", emailInput: "user@example.com", - expectedError: errors.New("login_email_not_found"), + 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", @@ -157,6 +191,14 @@ func TestResetRepoSql_GetUserByEmail(t *testing.T) { []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", @@ -176,6 +218,14 @@ func TestResetRepoSql_GetUserByEmail(t *testing.T) { []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", @@ -195,6 +245,14 @@ func TestResetRepoSql_GetUserByEmail(t *testing.T) { []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", @@ -202,6 +260,28 @@ func TestResetRepoSql_GetUserByEmail(t *testing.T) { 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 { @@ -210,8 +290,10 @@ func TestResetRepoSql_GetUserByEmail(t *testing.T) { db, dbMock := setupSqlMock(t) - repo, _ := users.NewRepoPostgresSql(db) + 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) @@ -220,7 +302,7 @@ func TestResetRepoSql_GetUserByEmail(t *testing.T) { assert.Equal(t, testCase.expectedError, err) if err == nil { assert.Equal(t, testCase.userId, user.Id()) - //TODO: assert.Equal(t, testCase.userOrgs, user.Organizations()) + 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()) @@ -239,6 +321,7 @@ func TestResetRepoSql_GetUser(t *testing.T) { idInput int expectedError error query func(*sqlmock.ExpectedQuery) + queryOrgs func(*sqlmock.ExpectedQuery) userId int userOrgs []int userEmail string @@ -258,6 +341,42 @@ func TestResetRepoSql_GetUser(t *testing.T) { []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", @@ -268,11 +387,19 @@ func TestResetRepoSql_GetUser(t *testing.T) { { comment: "no rows", idInput: 43, - expectedError: errors.New("login_user_id_not_found"), + 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", @@ -286,8 +413,16 @@ func TestResetRepoSql_GetUser(t *testing.T) { []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}, + userOrgs: []int{54, 23, 32}, userEmail: "user@example.com", userToken: "", userActivated: false, @@ -305,6 +440,14 @@ func TestResetRepoSql_GetUser(t *testing.T) { []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", @@ -324,6 +467,14 @@ func TestResetRepoSql_GetUser(t *testing.T) { []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", @@ -331,6 +482,53 @@ func TestResetRepoSql_GetUser(t *testing.T) { 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 { @@ -341,6 +539,7 @@ func TestResetRepoSql_GetUser(t *testing.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) @@ -349,7 +548,7 @@ func TestResetRepoSql_GetUser(t *testing.T) { assert.Equal(t, testCase.expectedError, err) if err == nil { assert.Equal(t, testCase.userId, user.Id()) - //TODO: assert.Equal(t, testCase.userOrgs, user.Organizations()) + 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()) @@ -496,10 +695,11 @@ func TestResetRepoSql_AddUser(t *testing.T) { 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 - userOrgs []int userEmail string userToken string userActivated bool @@ -532,8 +732,14 @@ func TestResetRepoSql_AddUser(t *testing.T) { []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, - userOrgs: []int{54, 23}, userEmail: "user@example.info", userToken: "", userActivated: false, @@ -556,12 +762,21 @@ func TestResetRepoSql_AddUser(t *testing.T) { []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", @@ -574,7 +789,12 @@ func TestResetRepoSql_AddUser(t *testing.T) { expectedError: errors.New("db error"), checkUserStatementExec: func(query *sqlmock.ExpectedQuery) { query. - WithArgs("user@example.info").WillReturnError(users.UserNotFound) + 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. @@ -585,6 +805,8 @@ func TestResetRepoSql_AddUser(t *testing.T) { getUserByEmailQuery: func(query *sqlmock.ExpectedQuery) { }, + queryOrgs: func(query *sqlmock.ExpectedQuery) { + }, }, { comment: "get user by email error", @@ -614,6 +836,63 @@ func TestResetRepoSql_AddUser(t *testing.T) { 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) { + + }, }, } @@ -623,8 +902,12 @@ func TestResetRepoSql_AddUser(t *testing.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()) @@ -633,7 +916,6 @@ func TestResetRepoSql_AddUser(t *testing.T) { assert.Equal(t, testCase.expectedError, err) if err == nil { assert.Equal(t, testCase.userId, user.Id()) - //TODO: 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()) @@ -773,3 +1055,162 @@ func TestResetRepoSql_ActivateUser(t *testing.T) { 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() + } +} From ee3c0c452dc064208ff53e716ce5f632aadd018e Mon Sep 17 00:00:00 2001 From: mcgurn Date: Sun, 17 May 2020 18:26:23 -0600 Subject: [PATCH 16/23] unit tests for role table and support for login with org id --- middleware/jwtHandler.go | 13 +- middleware/jwtHandler_test.go | 81 +++-- mocks/mock_helper.go | 9 +- mocks/mock_roles_repo.go | 24 +- mocks/mock_user_helper.go | 9 +- mocks/testRouter.go | 33 +- passwords/basicHelper.go | 12 +- passwords/basicHelper_test.go | 12 +- passwords/helper.go | 2 +- preferences/handlers_test.go | 4 +- roles/PermissionTableJson.go | 42 +-- roles/Repo.go | 6 +- roles/handlers.go | 10 +- roles/handlers_test.go | 140 +++++++++ ...{PermissionTable.go => permissionTable.go} | 9 +- roles/permissionTableJson_test.go | 285 ++++++++++++++++++ roles/{Permissions.go => permissions.go} | 1 - roles/permissions_test.go | 86 ++++++ users/User.go | 10 +- users/basicHelper.go | 11 +- users/handlers_test.go | 18 +- users/oneTimePassword.go | 2 +- users/oneTimePassword_test.go | 4 +- utils/context.go | 23 ++ 24 files changed, 716 insertions(+), 130 deletions(-) create mode 100644 roles/handlers_test.go rename roles/{PermissionTable.go => permissionTable.go} (65%) create mode 100644 roles/permissionTableJson_test.go rename roles/{Permissions.go => permissions.go} (99%) create mode 100644 roles/permissions_test.go create mode 100644 utils/context.go diff --git a/middleware/jwtHandler.go b/middleware/jwtHandler.go index 8e05e78..ba52ee0 100644 --- a/middleware/jwtHandler.go +++ b/middleware/jwtHandler.go @@ -72,7 +72,7 @@ func MakeJwtMiddlewareFunc(router routing.Router, userRepo users.Repo, permRepo } //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 { @@ -105,10 +105,16 @@ func MakeJwtMiddlewareFunc(router routing.Router, userRepo users.Repo, permRepo 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...) { @@ -121,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 index d022773..26967e4 100644 --- a/middleware/jwtHandler_test.go +++ b/middleware/jwtHandler_test.go @@ -10,6 +10,8 @@ import ( "net/http/httptest" "testing" + "github.com/reaction-eng/restlib/utils" + "github.com/reaction-eng/restlib/roles" "github.com/reaction-eng/restlib/routing" @@ -111,7 +113,7 @@ func TestMakeJwtMiddlewareFunc(t *testing.T) { mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) - mockHelper.EXPECT().ValidateToken("Example Token").Return(0, "", errors.New("bad token")) + mockHelper.EXPECT().ValidateToken("Example Token").Return(0, 0, "", errors.New("bad token")) }, http.StatusForbidden, @@ -128,7 +130,7 @@ func TestMakeJwtMiddlewareFunc(t *testing.T) { mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) - mockHelper.EXPECT().ValidateToken("Example one two three").Return(0, "", errors.New("bad token")) + mockHelper.EXPECT().ValidateToken("Example one two three").Return(0, 0, "", errors.New("bad token")) }, http.StatusForbidden, @@ -145,7 +147,7 @@ func TestMakeJwtMiddlewareFunc(t *testing.T) { mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) - mockHelper.EXPECT().ValidateToken("Example Token").Return(100, "", nil) + mockHelper.EXPECT().ValidateToken("Example Token").Return(100, 1000, "", nil) mockUsers.EXPECT().GetUser(100).Times(1).Return(nil, nil) @@ -164,7 +166,7 @@ func TestMakeJwtMiddlewareFunc(t *testing.T) { mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) - mockHelper.EXPECT().ValidateToken("Example Token").Return(100, "", nil) + mockHelper.EXPECT().ValidateToken("Example Token").Return(100, 1000, "", nil) mockUser := mocks.NewMockUser(ctrl) @@ -185,7 +187,7 @@ func TestMakeJwtMiddlewareFunc(t *testing.T) { mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) - mockHelper.EXPECT().ValidateToken("Example Token").Return(100, "example@example.com", nil) + mockHelper.EXPECT().ValidateToken("Example Token").Return(100, 1000, "example@example.com", nil) mockUser := mocks.NewMockUser(ctrl) mockUser.EXPECT().Email().Times(1).Return("") @@ -207,7 +209,7 @@ func TestMakeJwtMiddlewareFunc(t *testing.T) { mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) - mockHelper.EXPECT().ValidateToken("Example Token").Return(100, "example@example.com", nil) + 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") @@ -233,15 +235,16 @@ func TestMakeJwtMiddlewareFunc(t *testing.T) { mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) - mockHelper.EXPECT().ValidateToken("Example Token").Return(100, "example@example.com", nil) + 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).Times(1).Return(&roles.Permissions{ + mockRoles.EXPECT().GetPermissions(mockUser, 1000).Times(1).Return(&roles.Permissions{ Permissions: []string{}, }, nil) }, @@ -262,15 +265,16 @@ func TestMakeJwtMiddlewareFunc(t *testing.T) { mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) - mockHelper.EXPECT().ValidateToken("Example Token").Return(100, "example@example.com", nil) + 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).Times(1).Return(&roles.Permissions{ + mockRoles.EXPECT().GetPermissions(mockUser, 1000).Times(1).Return(&roles.Permissions{ Permissions: []string{"req_permissio"}, }, nil) }, @@ -291,15 +295,16 @@ func TestMakeJwtMiddlewareFunc(t *testing.T) { mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) - mockHelper.EXPECT().ValidateToken("Example Token").Return(100, "example@example.com", nil) + 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).Times(1).Return(&roles.Permissions{ + mockRoles.EXPECT().GetPermissions(mockUser, 1000).Times(1).Return(&roles.Permissions{ Permissions: []string{}, }, errors.New("permission error")) }, @@ -308,6 +313,36 @@ func TestMakeJwtMiddlewareFunc(t *testing.T) { 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", @@ -320,22 +355,23 @@ func TestMakeJwtMiddlewareFunc(t *testing.T) { mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) - mockHelper.EXPECT().ValidateToken("Example Token").Return(100, "example@example.com", nil) + 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).Times(1).Return(&roles.Permissions{ + mockRoles.EXPECT().GetPermissions(mockUser, 1000).Times(1).Return(&roles.Permissions{ Permissions: []string{}, }, nil) }, http.StatusOK, "", true, - map[string]interface{}{"user": 100}, + map[string]interface{}{utils.UserKey: 100, utils.OrganizationKey: 1000}, }, { "get user context with private route with needed permissions", @@ -349,22 +385,23 @@ func TestMakeJwtMiddlewareFunc(t *testing.T) { mockRouter.EXPECT().GetRoute(gomock.Any()).Times(1).Return(route) - mockHelper.EXPECT().ValidateToken("Example Token").Return(100, "example@example.com", nil) + 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).Times(1).Return(&roles.Permissions{ + 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{}{"user": 100}, + map[string]interface{}{utils.UserKey: 100, utils.OrganizationKey: 1000}, }, } @@ -402,11 +439,13 @@ func TestMakeJwtMiddlewareFunc(t *testing.T) { // assert if testCase.next { - for k, v := range testCase.context { - ctx := context.WithValue(r.Context(), k, v) + if len(testCase.context) > 0 { + ctx := r.Context() + for k, v := range testCase.context { + ctx = context.WithValue(ctx, k, v) + } r = r.WithContext(ctx) } - assert.Equal(t, w, wResponse, testCase.description) assert.Equal(t, r, rResponse, testCase.description) assert.Equal(t, http.StatusOK, w.Result().StatusCode, testCase.description) diff --git a/mocks/mock_helper.go b/mocks/mock_helper.go index b82d566..c72951a 100644 --- a/mocks/mock_helper.go +++ b/mocks/mock_helper.go @@ -103,13 +103,14 @@ func (mr *MockHelperMockRecorder) ValidatePassword(arg0 interface{}) *gomock.Cal } // ValidateToken mocks base method -func (m *MockHelper) ValidateToken(arg0 string) (int, string, error) { +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].(string) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 + ret1, _ := ret[1].(int) + ret2, _ := ret[2].(string) + ret3, _ := ret[3].(error) + return ret0, ret1, ret2, ret3 } // ValidateToken indicates an expected call of ValidateToken diff --git a/mocks/mock_roles_repo.go b/mocks/mock_roles_repo.go index 94b0d07..214ef68 100644 --- a/mocks/mock_roles_repo.go +++ b/mocks/mock_roles_repo.go @@ -35,44 +35,44 @@ func (m *MockRolesRepo) EXPECT() *MockRolesRepoMockRecorder { } // GetPermissions mocks base method -func (m *MockRolesRepo) GetPermissions(arg0 users.User) (*roles.Permissions, error) { +func (m *MockRolesRepo) GetPermissions(arg0 users.User, arg1 int) (*roles.Permissions, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetPermissions", arg0) + 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 interface{}) *gomock.Call { +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) + 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 []string) error { +func (m *MockRolesRepo) SetRolesByName(arg0 users.User, arg1 int, arg2 []string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SetRolesByName", arg0, arg1) + 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 interface{}) *gomock.Call { +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) + 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) error { +func (m *MockRolesRepo) SetRolesByRoleId(arg0 users.User, arg1 int, arg2 []int) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SetRolesByRoleId", arg0, arg1) + 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 interface{}) *gomock.Call { +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) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetRolesByRoleId", reflect.TypeOf((*MockRolesRepo)(nil).SetRolesByRoleId), arg0, arg1, arg2) } diff --git a/mocks/mock_user_helper.go b/mocks/mock_user_helper.go index e2b73ae..ba8a0c2 100644 --- a/mocks/mock_user_helper.go +++ b/mocks/mock_user_helper.go @@ -420,13 +420,14 @@ func (mr *MockUserHelperMockRecorder) ValidatePassword(arg0 interface{}) *gomock } // ValidateToken mocks base method -func (m *MockUserHelper) ValidateToken(arg0 string) (int, string, error) { +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].(string) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 + ret1, _ := ret[1].(int) + ret2, _ := ret[2].(string) + ret3, _ := ret[3].(error) + return ret0, ret1, ret2, ret3 } // ValidateToken indicates an expected call of ValidateToken diff --git a/mocks/testRouter.go b/mocks/testRouter.go index 509d293..3a17a6d 100644 --- a/mocks/testRouter.go +++ b/mocks/testRouter.go @@ -5,26 +5,47 @@ import ( "net/http" "strings" - "github.com/reaction-eng/restlib/routing" "github.com/reaction-eng/restlib/users" + + "github.com/reaction-eng/restlib/utils" + + "github.com/reaction-eng/restlib/routing" ) type TestRouter struct { routes []routing.Route - user users.User + userId *int + orgId *int } -func NewTestRouter(routerProducer routing.RouteProducer, user users.User) *TestRouter { +func NewTestRouter(routerProducer routing.RouteProducer) *TestRouter { return &TestRouter{ routerProducer.GetRoutes(), - user, + nil, + nil, + } +} +func NewTestRouterWithUserId(routerProducer routing.RouteProducer, userId int, orgId int) *TestRouter { + return &TestRouter{ + routerProducer.GetRoutes(), + &userId, + &orgId, + } +} + +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.user != nil { - ctx := context.WithValue(r.Context(), "user", router.user.Id()) + 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) } diff --git a/passwords/basicHelper.go b/passwords/basicHelper.go index 4c6a650..592b9b2 100644 --- a/passwords/basicHelper.go +++ b/passwords/basicHelper.go @@ -92,17 +92,17 @@ func (helper *BasicHelper) TokenGenerator() string { return fmt.Sprintf("%x", b) } -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") } @@ -120,17 +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 index 0c2cded..8c6d201 100644 --- a/passwords/basicHelper_test.go +++ b/passwords/basicHelper_test.go @@ -212,42 +212,49 @@ func TestBasicHelper_ValidateToken(t *testing.T) { testCases := []struct { token string userId int + orgId int email string error error }{ { - "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjk4LCJFbWFpbCI6Im1hdHRAZXhhbXBsZS5jb20ifQ.zbpK1ZSeOTrsvSscE7KJqdhHfMgiOSHiu_2jOBsOLyA", + "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"), }, @@ -261,10 +268,11 @@ func TestBasicHelper_ValidateToken(t *testing.T) { helper := setupBasicHelper(t, mockCtrl) // act - userId, email, err := helper.ValidateToken(testCase.token) + 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) } diff --git a/passwords/helper.go b/passwords/helper.go index 8beaf5e..e1f7036 100644 --- a/passwords/helper.go +++ b/passwords/helper.go @@ -10,6 +10,6 @@ type Helper interface { 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/preferences/handlers_test.go b/preferences/handlers_test.go index b1f75df..0faa8d8 100644 --- a/preferences/handlers_test.go +++ b/preferences/handlers_test.go @@ -105,7 +105,7 @@ func TestHandler_handleUserPreferencesGet(t *testing.T) { handler := preferences.NewHandler(mockUserRepo, mockPrefRepo) - router := mocks.NewTestRouter(handler, testCase.user()) + router := mocks.NewTestRouterWithUser(handler, testCase.user(), 0) req := httptest.NewRequest("GET", "http://localhost/users/preferences", nil) w := httptest.NewRecorder() @@ -239,7 +239,7 @@ func TestHandler_handleUserPreferencesSet(t *testing.T) { handler := preferences.NewHandler(mockUserRepo, mockPrefRepo) - router := mocks.NewTestRouter(handler, testCase.user()) + router := mocks.NewTestRouterWithUser(handler, testCase.user(), 0) req := httptest.NewRequest("POST", "http://localhost/users/preferences", testCase.body) w := httptest.NewRecorder() diff --git a/roles/PermissionTableJson.go b/roles/PermissionTableJson.go index 8389fb3..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 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/Repo.go b/roles/Repo.go index afc7fea..e439932 100644 --- a/roles/Repo.go +++ b/roles/Repo.go @@ -14,15 +14,15 @@ type Repo interface { /** Get the user with the email. An error is thrown is not found */ - GetPermissions(user users.User) (*Permissions, error) + GetPermissions(user users.User, organizationId int) (*Permissions, error) /** Set the user's roles. Note this wipes out all current roles */ - SetRolesByRoleId(user users.User, roles []int) error + SetRolesByRoleId(user users.User, organizationId int, roles []int) error /** Set the user's roles. Note this wipes out all current roles */ - SetRolesByName(user users.User, roles []string) error + SetRolesByName(user users.User, organizationId int, roles []string) error } diff --git a/roles/handlers.go b/roles/handlers.go index 5e34439..8ef6f92 100644 --- a/roles/handlers.go +++ b/roles/handlers.go @@ -54,7 +54,11 @@ func (handler *Handler) GetRoutes() []routing.Route { 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) @@ -66,12 +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_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 99% rename from roles/Permissions.go rename to roles/permissions.go index bb3c57e..fa69fa0 100644 --- a/roles/Permissions.go +++ b/roles/permissions.go @@ -22,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/users/User.go b/users/User.go index 8d43dca..4721f80 100644 --- a/users/User.go +++ b/users/User.go @@ -5,7 +5,6 @@ package users //go:generate mockgen -destination=../mocks/mock_user.go -package=mocks github.com/reaction-eng/restlib/users User -//a struct to rep user account type User interface { //Return the user id Id() int @@ -31,3 +30,12 @@ type User interface { //Check to see if the user can login with a password PasswordLogin() bool } + +func InOrganization(user User, organization int) bool { + for _, org := range user.Organizations() { + if org == organization { + return true + } + } + return false +} diff --git a/users/basicHelper.go b/users/basicHelper.go index d604e98..32a330b 100644 --- a/users/basicHelper.go +++ b/users/basicHelper.go @@ -216,7 +216,7 @@ func (helper *BasicHelper) Login(userPassword string, organizationId int, user U } // make sure user is in org - if !inList(user.Organizations(), organizationId) { + if !InOrganization(user, organizationId) { return nil, errors.New("user_not_in_organization") } @@ -243,12 +243,3 @@ func (helper *BasicHelper) Login(userPassword string, organizationId int, user U return user, nil } - -func inList(organizationList []int, organization int) bool { - for _, org := range organizationList { - if org == organization { - return true - } - } - return false -} diff --git a/users/handlers_test.go b/users/handlers_test.go index 0d647a5..bd22c7b 100644 --- a/users/handlers_test.go +++ b/users/handlers_test.go @@ -98,7 +98,7 @@ func TestHandler_handleUserCreate(t *testing.T) { } handler := users.NewHandler(mockHelper, true) - router := mocks.NewTestRouter(handler, nil) // not logged in + router := mocks.NewTestRouter(handler) // not logged in req := httptest.NewRequest("POST", "http://localhost/users/new", testCase.body) w := httptest.NewRecorder() @@ -214,7 +214,7 @@ func TestHandler_handleUserLogin(t *testing.T) { mockHelper.EXPECT().Login("new password", 34, mockUser).Times(testCase.loginCount).Return(mockUser, testCase.loginError) handler := users.NewHandler(mockHelper, true) - router := mocks.NewTestRouter(handler, nil) // not logged in + router := mocks.NewTestRouter(handler) // not logged in req := httptest.NewRequest("POST", "http://localhost/users/login", testCase.body) w := httptest.NewRecorder() @@ -358,7 +358,7 @@ func TestHandler_handleUserUpdate(t *testing.T) { mockHelper.EXPECT().Update(34, testCase.expectedUserAfterDecode()).Times(testCase.updateCount).Return(testCase.expectedUserAfterDecode(), testCase.updateError) handler := users.NewHandler(mockHelper, true) - router := mocks.NewTestRouter(handler, testCase.loggedInUser) // not logged in + router := mocks.NewTestRouterWithUser(handler, testCase.loggedInUser, 0) // not logged in req := httptest.NewRequest("PUT", "http://localhost/users/", testCase.body) w := httptest.NewRecorder() @@ -437,7 +437,7 @@ func TestHandler_handleUserGet(t *testing.T) { mockHelper.EXPECT().GetUser(34).Times(testCase.getUserCount).Return(testCase.getUserUser(), testCase.getUserError) handler := users.NewHandler(mockHelper, true) - router := mocks.NewTestRouter(handler, testCase.loggedInUser) // not logged in + router := mocks.NewTestRouterWithUser(handler, testCase.loggedInUser, 0) req := httptest.NewRequest("GET", "http://localhost/users/", nil) w := httptest.NewRecorder() @@ -515,7 +515,7 @@ func TestHandler_handlePasswordUpdate(t *testing.T) { mockHelper.EXPECT().PasswordChange(34, "user@example.info", "new password", "old password").Times(testCase.passwordChangeCount).Return(testCase.passwordChangeError) handler := users.NewHandler(mockHelper, true) - router := mocks.NewTestRouter(handler, testCase.loggedInUser) // not logged in + router := mocks.NewTestRouterWithUser(handler, testCase.loggedInUser, 0) req := httptest.NewRequest("POST", "http://localhost/users/password/change", testCase.body) w := httptest.NewRecorder() @@ -645,7 +645,7 @@ func TestHandler_handlePasswordResetGet(t *testing.T) { mockHelper.EXPECT().IssueResetRequest(token, 34, "user@example.com").Times(testCase.issueResetRequestCount).Return(testCase.issueResetRequestError) handler := users.NewHandler(mockHelper, true) - router := mocks.NewTestRouter(handler, nil) // not logged in + router := mocks.NewTestRouter(handler) // not logged in req := httptest.NewRequest("GET", testCase.url, nil) w := httptest.NewRecorder() @@ -815,7 +815,7 @@ func TestHandler_handlePasswordResetPut(t *testing.T) { mockHelper.EXPECT().UseToken(reqId).Times(testCase.useTokenCount).Return(testCase.userTokenError) handler := users.NewHandler(mockHelper, true) - router := mocks.NewTestRouter(handler, nil) // not logged in + router := mocks.NewTestRouter(handler) // not logged in req := httptest.NewRequest("POST", "http://localhost/users/password/reset", testCase.body) w := httptest.NewRecorder() @@ -981,7 +981,7 @@ func TestHandler_handleUserActivationPut(t *testing.T) { mockHelper.EXPECT().UseToken(reqId).Times(testCase.useTokenCount).Return(testCase.userTokenError) handler := users.NewHandler(mockHelper, true) - router := mocks.NewTestRouter(handler, nil) // not logged in + router := mocks.NewTestRouter(handler) // not logged in req := httptest.NewRequest("POST", "http://localhost/users/activate", testCase.body) w := httptest.NewRecorder() @@ -1134,7 +1134,7 @@ func TestHandler_handleUserActivationGet(t *testing.T) { mockHelper.EXPECT().IssueActivationRequest(token, 34, "user@example.com").Times(testCase.issueActivationRequestCount).Return(testCase.issueActivationRequestError) handler := users.NewHandler(mockHelper, true) - router := mocks.NewTestRouter(handler, nil) // not logged in + router := mocks.NewTestRouter(handler) // not logged in req := httptest.NewRequest("GET", testCase.url, nil) w := httptest.NewRecorder() diff --git a/users/oneTimePassword.go b/users/oneTimePassword.go index 1f0eb33..68ae7b7 100644 --- a/users/oneTimePassword.go +++ b/users/oneTimePassword.go @@ -141,7 +141,7 @@ func (handler *OneTimePasswordHandler) handleOneTimePasswordLoginPut(w http.Resp return } - if !inList(user.Organizations(), info.OrganizationId) { + if !InOrganization(user, info.OrganizationId) { utils.ReturnJsonStatus(w, http.StatusForbidden, false, UserNotInOrganization.Error()) return } diff --git a/users/oneTimePassword_test.go b/users/oneTimePassword_test.go index b31417b..d2ae4e0 100644 --- a/users/oneTimePassword_test.go +++ b/users/oneTimePassword_test.go @@ -204,7 +204,7 @@ func TestHandler_handleOneTimePasswordGet(t *testing.T) { mockHelper.EXPECT().IssueOneTimePasswordRequest(token, 34, "user@example.com").Times(testCase.issueOneTimePasswordRequestCount).Return(testCase.issueOneTimePasswordRequestError) handler := users.NewOneTimePasswordHandler(mockHelper) - router := mocks.NewTestRouter(handler, nil) // not logged in + router := mocks.NewTestRouter(handler) // not logged in req := httptest.NewRequest("GET", testCase.url, nil) w := httptest.NewRecorder() @@ -450,7 +450,7 @@ func TestHandler_handleOneTimePasswordLoginPut(t *testing.T) { mockHelper.EXPECT().CreateJWTToken(34, 45, "user@example.com").Times(testCase.createJwtTokenCount).Return("jwtToken") handler := users.NewOneTimePasswordHandler(mockHelper) - router := mocks.NewTestRouter(handler, nil) // not logged in + router := mocks.NewTestRouter(handler) // not logged in req := httptest.NewRequest("POST", "http://localhost/users/onetimelogin", testCase.body) w := httptest.NewRecorder() 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 +} From 09099b503f5ee0b49a6298476bfd03a8690d6958 Mon Sep 17 00:00:00 2001 From: mcgurn Date: Sat, 23 May 2020 15:36:29 -0600 Subject: [PATCH 17/23] added tests for the permission table --- middleware/jwtHandler_test.go | 16 +- migrations/mysql/4_initial_roles.sql | 5 + migrations/postgres/4_initial_roles.sql | 5 + mocks/mock_permissionTable.go | 62 ++ roles/RepoSql.go | 280 --------- ...ionTableJson.go => permissionTableJson.go} | 0 roles/{Repo.go => repo.go} | 12 - roles/repoSql.go | 239 ++++++++ roles/repoSql_test.go | 577 ++++++++++++++++++ 9 files changed, 893 insertions(+), 303 deletions(-) create mode 100644 migrations/mysql/4_initial_roles.sql create mode 100644 migrations/postgres/4_initial_roles.sql create mode 100644 mocks/mock_permissionTable.go delete mode 100644 roles/RepoSql.go rename roles/{PermissionTableJson.go => permissionTableJson.go} (100%) rename roles/{Repo.go => repo.go} (71%) create mode 100644 roles/repoSql.go create mode 100644 roles/repoSql_test.go diff --git a/middleware/jwtHandler_test.go b/middleware/jwtHandler_test.go index 26967e4..8cc2303 100644 --- a/middleware/jwtHandler_test.go +++ b/middleware/jwtHandler_test.go @@ -4,17 +4,14 @@ package middleware_test import ( - "context" "errors" "net/http" "net/http/httptest" "testing" - "github.com/reaction-eng/restlib/utils" - "github.com/reaction-eng/restlib/roles" - "github.com/reaction-eng/restlib/routing" + "github.com/reaction-eng/restlib/utils" "github.com/golang/mock/gomock" @@ -439,15 +436,12 @@ func TestMakeJwtMiddlewareFunc(t *testing.T) { // assert if testCase.next { - if len(testCase.context) > 0 { - ctx := r.Context() - for k, v := range testCase.context { - ctx = context.WithValue(ctx, k, v) - } - r = r.WithContext(ctx) + 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, r, rResponse, testCase.description) assert.Equal(t, http.StatusOK, w.Result().StatusCode, testCase.description) assert.Empty(t, w.Body.String(), testCase.description) } else { 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/4_initial_roles.sql b/migrations/postgres/4_initial_roles.sql new file mode 100644 index 0000000..1514885 --- /dev/null +++ b/migrations/postgres/4_initial_roles.sql @@ -0,0 +1,5 @@ +-- +migrate Up +CREATE TABLE IF NOT EXISTS role (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_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/roles/RepoSql.go b/roles/RepoSql.go deleted file mode 100644 index 915677b..0000000 --- a/roles/RepoSql.go +++ /dev/null @@ -1,280 +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 ( - "database/sql" - "log" - - "github.com/reaction-eng/restlib/users" -) - -/** -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/PermissionTableJson.go b/roles/permissionTableJson.go similarity index 100% rename from roles/PermissionTableJson.go rename to roles/permissionTableJson.go diff --git a/roles/Repo.go b/roles/repo.go similarity index 71% rename from roles/Repo.go rename to roles/repo.go index e439932..9df6f5f 100644 --- a/roles/Repo.go +++ b/roles/repo.go @@ -7,22 +7,10 @@ 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, organizationId int) (*Permissions, error) - /** - Set the user's roles. Note this wipes out all current roles - */ SetRolesByRoleId(user users.User, organizationId int, roles []int) error - /** - Set the user's roles. Note this wipes out all current roles - */ 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() + } +} From 5a94a8b0ab7462fb4f99a4c81e1c90d1f3ea61f5 Mon Sep 17 00:00:00 2001 From: mcgurn Date: Sun, 31 May 2020 11:03:38 -0600 Subject: [PATCH 18/23] bug and test fixes --- cache/cache.go | 2 + cache/redis.go | 4 +- configuration/json.go | 3 - file/directory.go | 18 -- file/document.go | 15 -- file/item.go | 36 ++- file/listing.go | 50 ++++ file/storage.go | 11 +- google/{gDrive.go => drive.go} | 203 +++----------- google/gDirectory.go | 166 ------------ google/gDirectory_test.go | 341 ------------------------ google/gDocument.go | 55 ---- google/gDocument_test.go | 181 ------------- google/gEvent.go | 38 --- google/gFile.go | 33 --- google/gForm.go | 64 ----- migrations/migrations.go | 4 +- migrations/postgres/3_initial_users.sql | 8 +- migrations/postgres/4_initial_roles.sql | 2 +- mocks/mock_cache.go | 88 ++++++ mocks/mock_directory.go | 131 --------- mocks/mock_document.go | 132 --------- mocks/mock_item.go | 175 ------------ mocks/mock_storage.go | 154 ++++++++--- mocks/testRouter.go | 64 +++-- static/RepoCache.go | 1 - 26 files changed, 387 insertions(+), 1592 deletions(-) delete mode 100644 file/directory.go delete mode 100644 file/document.go create mode 100644 file/listing.go rename google/{gDrive.go => drive.go} (74%) delete mode 100644 google/gDirectory.go delete mode 100644 google/gDirectory_test.go delete mode 100644 google/gDocument.go delete mode 100644 google/gDocument_test.go delete mode 100644 google/gEvent.go delete mode 100644 google/gFile.go delete mode 100644 google/gForm.go create mode 100644 mocks/mock_cache.go delete mode 100644 mocks/mock_directory.go delete mode 100644 mocks/mock_document.go delete mode 100644 mocks/mock_item.go diff --git a/cache/cache.go b/cache/cache.go index 51ed087..333ba67 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -3,6 +3,8 @@ package cache +//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 diff --git a/cache/redis.go b/cache/redis.go index 1575c05..f9ab30e 100644 --- a/cache/redis.go +++ b/cache/redis.go @@ -36,12 +36,14 @@ func NewObjectRedisCache(redis *redis.Ring) *Redis { return &infoRepo } -func (repo *Redis) 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 } func (repo *Redis) Set(key string, item interface{}) error { diff --git a/configuration/json.go b/configuration/json.go index 6498165..8f05ea4 100644 --- a/configuration/json.go +++ b/configuration/json.go @@ -28,9 +28,6 @@ func NewJson(configFiles ...string) (*Json, error) { }, } - // 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 diff --git a/file/directory.go b/file/directory.go deleted file mode 100644 index 9413a7f..0000000 --- a/file/directory.go +++ /dev/null @@ -1,18 +0,0 @@ -package file - -//go:generate mockgen -destination=../mocks/mock_directory.go -package=mocks github.com/reaction-eng/restlib/file Directory - -type Directory interface { - Item - - GetType() string - - GetItems() []Item - - GetParentId() string - - ForEach(doOnFolder bool, do ItemFunc) -} - -//Define a function -type ItemFunc func(item Item) diff --git a/file/document.go b/file/document.go deleted file mode 100644 index 02eefbd..0000000 --- a/file/document.go +++ /dev/null @@ -1,15 +0,0 @@ -package file - -//go:generate mockgen -destination=../mocks/mock_document.go -package=mocks github.com/reaction-eng/restlib/file Document - -type Document interface { - Item - - Type() string - - Preview() string - - ThumbnailUrl() string - - ParentId() string -} diff --git a/file/item.go b/file/item.go index a494b97..ca10782 100644 --- a/file/item.go +++ b/file/item.go @@ -1,18 +1,32 @@ package file -//go:generate mockgen -destination=../mocks/mock_storage.go -package=mocks github.com/reaction-eng/restlib/file Item - import ( "time" ) -/** - * An interface type to hold a document or directory - */ -type Item interface { - GetId() string - GetName() string - GetDate() *time.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 @@ -23,8 +37,8 @@ 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() + iDate := a[i].Date + jDate := a[j].Date //If if iDate == nil && jDate == nil { diff --git a/file/listing.go b/file/listing.go new file mode 100644 index 0000000..a7c5bc5 --- /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 index 772f54c..3136bbf 100644 --- a/file/storage.go +++ b/file/storage.go @@ -1,6 +1,6 @@ package file -//go:generate mockgen -destination=../mocks/mock_item.go -package=mocks github.com/reaction-eng/restlib/file Storage +//go:generate mockgen -destination=../mocks/mock_storage.go -package=mocks github.com/reaction-eng/restlib/file Storage import "io" @@ -8,12 +8,11 @@ type Storage interface { GetArbitraryFile(id string) (io.ReadCloser, error) PostArbitraryFile(fileName string, parent string, file io.Reader, mime string) (string, error) - BuildFileHierarchy(dirId string, buildPreview bool, includeFilter func(fileType string) bool) Directory - BuildFormHierarchy(dirId string) Directory - GetFilePreview(id string) string - GetFileThumbnailUrl(id string) + 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{}) + GetFileAsInterface(id string, inter interface{}) error GetFirstFileMatching(dirId string, name string) (io.ReadCloser, error) } diff --git a/google/gDrive.go b/google/drive.go similarity index 74% rename from google/gDrive.go rename to google/drive.go index e229c71..a5d0f92 100644 --- a/google/gDrive.go +++ b/google/drive.go @@ -10,7 +10,6 @@ import ( "io" "io/ioutil" "log" - "path/filepath" "regexp" "strings" "time" @@ -29,24 +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(configuration configuration.Configuration) *Drive { +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: configuration.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: configuration.GetStringFatal("google_auth_email"), - PrivateKey: []byte(configuration.GetStringFatal("google_auth_key")), + Email: email, + PrivateKey: []byte(privateKey), Scopes: []string{ drive.DriveMetadataReadonlyScope, drive.DriveReadonlyScope, @@ -62,13 +72,7 @@ func NewDrive(configuration configuration.Configuration) *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, _ = configuration.GetInt("preview_length") - return gInter + return gInter, err } //See if starts with a date and name @@ -130,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) *gDirectory { +func (gog *Drive) BuildListing(dirId string, previewLength int, includeFilter func(fileType string) bool) (*file.Listing, error) { //Get this item folderInfo, err := gog.connection.Files. @@ -143,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 := &gDirectory{ - gFile: gFile{ - Id: folderInfo.Id, - Name: name, - }, - Type: folderInfo.MimeType, - Items: make([]file.Item, 0), - } + dir := file.NewListing() + dir.Id = folderInfo.Id + dir.Name = name //If there is a date add it if date != nil { @@ -174,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 @@ -184,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) + + 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 := &gDocument{ - gFile: gFile{ - 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 @@ -224,100 +221,13 @@ func (gog *Drive) BuildFileHierarchy(dirId string, buildPreview bool, includeFil } } - return dir -} - -/** -Builds all of the forms and downloads them at the same time -*/ -func (gog *Drive) BuildFormHierarchy(dirId string) *gDirectory { - - //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 := &gDirectory{ - gFile: gFile{ - Id: folderInfo.Id, - Name: folderInfo.Name, - }, - Type: folderInfo.MimeType, - Items: make([]file.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 { @@ -347,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) } @@ -356,38 +265,6 @@ func (gog *Drive) GetFilePreview(id string) string { } -/** -* Method to get the information hierarchy - */ -func (gog *Drive) downloadForm(id string) (*gForm, 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 := &gForm{} - - //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 */ diff --git a/google/gDirectory.go b/google/gDirectory.go deleted file mode 100644 index a2af9e7..0000000 --- a/google/gDirectory.go +++ /dev/null @@ -1,166 +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" - "fmt" - - "github.com/reaction-eng/restlib/file" -) - -type gDirectory struct { - gFile - - //Keep the type for directory anyways - Type string `json:"type"` - - //Hold a list of Items - Items []file.Item `json:"Items"` - - //Keep the parent Id, unless this is root and then it is null - ParentId string `json:"parentid"` -} - -func (dir *gDirectory) GetType() string { - return dir.Type -} - -func (dir *gDirectory) GetItems() []file.Item { - return dir.Items -} - -func (dir *gDirectory) GetParentId() string { - return dir.ParentId -} - -func (dir *gDirectory) ForEach(doOnFolder bool, do file.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.(*gDirectory); isDir { - asDir.ForEach(doOnFolder, do) - } else { - //Just apply the function - do(item) - } - } -} - -/** -Custom marashaler -*/ -func (dir gDirectory) MarshalJSON() ([]byte, error) { - //Store a ddir copy - type fakeItem gDirectory - - //define the item type - type ItemWithType struct { - fakeItem - InternalItemType string - } - - return json.Marshal(ItemWithType{ - (fakeItem)(dir), - "gDirectory", - }) -} - -func (dir *gDirectory) 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 gDirectory - - //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([]file.Item, len(rawItemsList)) - - for index, rawMessage := range rawItemsList { - var m map[string]interface{} - - 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 "gDocument": - var tmp gDocument - err := json.Unmarshal(*rawMessage, &tmp) - if err != nil { - return err - } - dir.Items[index] = &tmp - break - case "gDirectory": - var tmp gDirectory - err := json.Unmarshal(*rawMessage, &tmp) - if err != nil { - return err - } - dir.Items[index] = &tmp - break - case "gForm": - var tmp gForm - err := json.Unmarshal(*rawMessage, &tmp) - if err != nil { - return err - } - dir.Items[index] = &tmp - break - case "gEvent": - var tmp gEvent - err := json.Unmarshal(*rawMessage, &tmp) - if err != nil { - return err - } - dir.Items[index] = &tmp - break - default: - var tmp gFile - err := json.Unmarshal(*rawMessage, &tmp) - if err != nil { - return err - } - dir.Items[index] = &tmp - break - } - } - - return nil -} diff --git a/google/gDirectory_test.go b/google/gDirectory_test.go deleted file mode 100644 index 287e2c4..0000000 --- a/google/gDirectory_test.go +++ /dev/null @@ -1,341 +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" - "sort" - "testing" - "time" - - "github.com/reaction-eng/restlib/file" - - "github.com/stretchr/testify/assert" -) - -func TestGDirectory_GetId(t *testing.T) { - // arrange - referenceTime := time.Unix(1574622126, 0) - - gDirectory := gDirectory{ - gFile: gFile{ - Id: "12345abc", - Name: "testName", - HideListing: false, - Date: &referenceTime, - }, - } - - // act - testId := gDirectory.GetId() - - //assert - assert.Equal(t, "12345abc", testId) -} - -func TestGDirectory_GetName(t *testing.T) { - // arrange - referenceTime := time.Unix(1574622126, 0) - - gDirectory := gDirectory{ - gFile: gFile{ - Id: "12345abc", - Name: "testName", - HideListing: false, - Date: &referenceTime, - }, - } - - // act - testName := gDirectory.GetName() - - //assert - assert.Equal(t, "testName", testName) -} - -func TestGDirectory_GetDate(t *testing.T) { - // arrange - referenceTime := time.Unix(1574622126, 0) - - gDirectory := gDirectory{ - gFile: gFile{ - Id: "12345abc", - Name: "testName", - HideListing: false, - Date: &referenceTime, - }, - } - - // act - testDate := gDirectory.GetDate() - - //assert - assert.Equal(t, &referenceTime, testDate) -} - -func TestGDirectory_GetType(t *testing.T) { - // arrange - referenceTime := time.Unix(1574622126, 0) - - gDirectory := gDirectory{ - gFile: gFile{ - Id: "12345abc", - Name: "testName", - HideListing: false, - Date: &referenceTime, - }, - Type: "testFileType", - } - - // act - testFileType := gDirectory.GetType() - - //assert - assert.Equal(t, "testFileType", testFileType) -} - -func TestGDirectory_GetItems(t *testing.T) { - // arrange - referenceTime := time.Unix(1574622126, 0) - - expectedItems := []file.Item{ - gFile{ - Id: "subItemId1", - Name: "subItemName1", - HideListing: false, - Date: &referenceTime, - }, - gFile{ - Id: "subItemName2", - Name: "testName", - HideListing: false, - Date: &referenceTime, - }, - } - - gDirectory := gDirectory{ - gFile: gFile{ - Id: "12345abc", - Name: "testName", - HideListing: false, - Date: &referenceTime, - }, - Type: "testFileType", - Items: expectedItems, - } - - // act - testItems := gDirectory.GetItems() - - //assert - assert.Equal(t, expectedItems, testItems) -} - -func TestGDirectory_GetParentId(t *testing.T) { - // arrange - referenceTime := time.Unix(1574622126, 0) - - gDirectory := gDirectory{ - gFile: gFile{ - Id: "12345abc", - Name: "testName", - HideListing: false, - Date: &referenceTime, - }, - Type: "testFileType", - ParentId: "parentId123", - } - - // act - testParentId := gDirectory.GetParentId() - - //assert - assert.Equal(t, "parentId123", testParentId) -} - -func TestGDirectory_ForEach(t *testing.T) { - // arrange - referenceTime := time.Unix(1574622126, 0) - - items := []file.Item{ - &gFile{ - Id: "subItemId1", - Name: "subItemName1", - HideListing: false, - Date: &referenceTime, - }, - &gFile{ - Id: "subItemName2", - Name: "testName", - HideListing: false, - Date: &referenceTime, - }, - &gDirectory{ - gFile: gFile{ - Id: "subDirId1", - }, - Type: "", - ParentId: "", - Items: []file.Item{ - &gFile{ - Id: "subSubItemId1", - Name: "subSubItemName1", - HideListing: false, - Date: &referenceTime, - }, - }, - }, - } - - gDirectory := gDirectory{ - gFile: gFile{ - Id: "12345abc", - Name: "testName", - HideListing: false, - Date: &referenceTime, - }, - Type: "testFileType", - Items: items, - } - - testCases := []struct { - includeDir bool - expected []string - }{ - { - includeDir: false, - expected: []string{"subItemId1", "subItemName2", "subSubItemId1"}, - }, - { - includeDir: true, - expected: []string{"12345abc", "subItemId1", "subItemName2", "subSubItemId1", "subDirId1"}, - }, - } - for _, testCase := range testCases { - // act - calledCommands := make([]string, 0) - gDirectory.ForEach(testCase.includeDir, func(item file.Item) { - calledCommands = append(calledCommands, item.GetId()) - }) - - //assert - sort.Strings(calledCommands) - sort.Strings(testCase.expected) - assert.Equal(t, testCase.expected, calledCommands) - } -} - -func TestGDirectory_MarshalJSON(t *testing.T) { - // arrange - referenceTime := time.Unix(1574622126, 0) - - items := []file.Item{ - &gDocument{ - gFile: gFile{ - Id: "subItemId1", - Name: "subItemName1", - HideListing: false, - Date: &referenceTime, - }, - Preview: "Preview123", - }, - &gFile{ - Id: "subItemName2", - Name: "testName", - HideListing: false, - Date: &referenceTime, - }, - &gDirectory{ - gFile: gFile{ - Id: "subDirId1", - }, - Type: "", - ParentId: "", - Items: []file.Item{ - &gFile{ - Id: "subSubItemId1", - Name: "subSubItemName1", - HideListing: false, - Date: &referenceTime, - }, - }, - }, - } - - gDirectory := gDirectory{ - gFile: gFile{ - Id: "12345abc", - Name: "testName", - HideListing: false, - Date: &referenceTime, - }, - Type: "testFileType", - Items: items, - } - - // act - jsonGDirectory, _ := json.Marshal(gDirectory) - - //assert - assert.Equal(t, `{"Id":"12345abc","name":"testName","hideListing":false,"date":"2019-11-24T12:02:06-07:00","type":"testFileType","Items":[{"Id":"subItemId1","name":"subItemName1","hideListing":false,"date":"2019-11-24T12:02:06-07:00","type":"","preview":"Preview123","thumbnail":"","parentid":"","InternalItemType":"gDocument"},{"Id":"subItemName2","name":"testName","hideListing":false,"date":"2019-11-24T12:02:06-07:00"},{"Id":"subDirId1","name":"","hideListing":false,"date":null,"type":"","Items":[{"Id":"subSubItemId1","name":"subSubItemName1","hideListing":false,"date":"2019-11-24T12:02:06-07:00"}],"parentid":"","InternalItemType":"gDirectory"}],"parentid":"","InternalItemType":"gDirectory"}`, string(jsonGDirectory)) -} - -func TestGDirectory_UnmarshalJSON(t *testing.T) { - // arrange - referenceTime := time.Unix(1574622126, 0) - - expectedItems := []file.Item{ - &gDocument{ - gFile: gFile{ - Id: "subItemId1", - Name: "subItemName1", - HideListing: false, - Date: &referenceTime, - }, - Preview: "Preview123", - }, - &gFile{ - Id: "subItemName2", - Name: "testName", - HideListing: false, - Date: &referenceTime, - }, - &gDirectory{ - gFile: gFile{ - Id: "subDirId1", - }, - Type: "", - ParentId: "", - Items: []file.Item{ - &gFile{ - Id: "subSubItemId1", - Name: "subSubItemName1", - HideListing: false, - Date: &referenceTime, - }, - }, - }, - } - - expectedGDirectory := gDirectory{ - gFile: gFile{ - Id: "12345abc", - Name: "testName", - HideListing: false, - Date: &referenceTime, - }, - Type: "testFileType", - Items: expectedItems, - } - - jsonString := `{"Id":"12345abc","name":"testName","hideListing":false,"date":"2019-11-24T12:02:06-07:00","type":"testFileType","Items":[{"Id":"subItemId1","name":"subItemName1","hideListing":false,"date":"2019-11-24T12:02:06-07:00","type":"","preview":"Preview123","thumbnail":"","parentid":"","InternalItemType":"gDocument"},{"Id":"subItemName2","name":"testName","hideListing":false,"date":"2019-11-24T12:02:06-07:00"},{"Id":"subDirId1","name":"","hideListing":false,"date":null,"type":"","Items":[{"Id":"subSubItemId1","name":"subSubItemName1","hideListing":false,"date":"2019-11-24T12:02:06-07:00"}],"parentid":"","InternalItemType":"gDirectory"}],"parentid":"","InternalItemType":"gDirectory"}` - - // act - var testGDirectory gDirectory - err := json.Unmarshal([]byte(jsonString), &testGDirectory) - - //assert - assert.Nil(t, err) - assert.EqualValues(t, expectedGDirectory, testGDirectory) -} diff --git a/google/gDocument.go b/google/gDocument.go deleted file mode 100644 index 84022f0..0000000 --- a/google/gDocument.go +++ /dev/null @@ -1,55 +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" -) - -type gDocument struct { - gFile - - 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 (doc *gDocument) GetType() string { - return doc.Type -} - -func (doc *gDocument) GetParentId() string { - return doc.ParentId -} - -func (doc *gDocument) GetPreview() string { - return doc.Preview -} - -func (doc *gDocument) GetThumbnailUrl() string { - return doc.ThumbnailUrl -} - -func (doc gDocument) MarshalJSON() ([]byte, error) { - //Store a dir copy - type fakeItem gDocument - - //define the item type - type ItemWithType struct { - fakeItem - InternalItemType string - } - - return json.Marshal(ItemWithType{ - (fakeItem)(doc), - "gDocument", - }) -} diff --git a/google/gDocument_test.go b/google/gDocument_test.go deleted file mode 100644 index 93b7def..0000000 --- a/google/gDocument_test.go +++ /dev/null @@ -1,181 +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" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestGDocument_GetId(t *testing.T) { - // arrange - referenceTime := time.Unix(1574622126, 0) - - gDocument := gDocument{ - gFile: gFile{ - Id: "12345abc", - Name: "testName", - HideListing: false, - Date: &referenceTime, - }, - } - - // act - testId := gDocument.GetId() - - //assert - assert.Equal(t, "12345abc", testId) -} - -func TestGDocument_GetName(t *testing.T) { - // arrange - referenceTime := time.Unix(1574622126, 0) - - gDocument := gDocument{ - gFile: gFile{ - Id: "12345abc", - Name: "testName", - HideListing: false, - Date: &referenceTime, - }, - } - - // act - testName := gDocument.GetName() - - //assert - assert.Equal(t, "testName", testName) -} - -func TestGDocument_GetDate(t *testing.T) { - // arrange - referenceTime := time.Unix(1574622126, 0) - - gDocument := gDocument{ - gFile: gFile{ - Id: "12345abc", - Name: "testName", - HideListing: false, - Date: &referenceTime, - }, - } - - // act - testDate := gDocument.GetDate() - - //assert - assert.Equal(t, &referenceTime, testDate) -} - -func TestGDocument_GetType(t *testing.T) { - // arrange - referenceTime := time.Unix(1574622126, 0) - - gDocument := gDocument{ - gFile: gFile{ - Id: "12345abc", - Name: "testName", - HideListing: false, - Date: &referenceTime, - }, - Type: "testFileType", - } - - // act - testFileType := gDocument.GetType() - - //assert - assert.Equal(t, "testFileType", testFileType) -} - -func TestGDocument_GetPreview(t *testing.T) { - // arrange - referenceTime := time.Unix(1574622126, 0) - - gDocument := gDocument{ - gFile: gFile{ - Id: "12345abc", - Name: "testName", - HideListing: false, - Date: &referenceTime, - }, - Preview: "testFilePreview", - } - - // act - testPreview := gDocument.GetPreview() - - //assert - assert.Equal(t, "testFilePreview", testPreview) -} - -func TestGDocument_GetThumbnailUrl(t *testing.T) { - // arrange - referenceTime := time.Unix(1574622126, 0) - - gDocument := gDocument{ - gFile: gFile{ - Id: "12345abc", - Name: "testName", - HideListing: false, - Date: &referenceTime, - }, - ThumbnailUrl: "thumbnailUrl", - } - - // act - testThumbnailUrl := gDocument.GetThumbnailUrl() - - //assert - assert.Equal(t, "thumbnailUrl", testThumbnailUrl) -} - -func TestGDocument_GetParentId(t *testing.T) { - // arrange - referenceTime := time.Unix(1574622126, 0) - - gDocument := gDocument{ - gFile: gFile{ - Id: "12345abc", - Name: "testName", - HideListing: false, - Date: &referenceTime, - }, - Type: "testFileType", - ParentId: "parentId123", - } - - // act - testParentId := gDocument.GetParentId() - - //assert - assert.Equal(t, "parentId123", testParentId) -} - -func TestGDocument_MarshalJSON(t *testing.T) { - // arrange - referenceTime := time.Unix(1574622126, 0) - - gDocument := gDocument{ - gFile: gFile{ - Id: "12345abc", - Name: "testName", - HideListing: false, - Date: &referenceTime, - }, - Type: "testFileType", - Preview: "testFilePreview", - ThumbnailUrl: "utl//url", - ParentId: "IHaveNoFather", - } - - // act - jsonGDocument, _ := json.Marshal(gDocument) - - //assert - assert.Equal(t, `{"Id":"12345abc","name":"testName","hideListing":false,"date":"2019-11-24T12:02:06-07:00","type":"testFileType","preview":"testFilePreview","thumbnail":"utl//url","parentid":"IHaveNoFather","InternalItemType":"gDocument"}`, string(jsonGDocument)) -} diff --git a/google/gEvent.go b/google/gEvent.go deleted file mode 100644 index 1339b1c..0000000 --- a/google/gEvent.go +++ /dev/null @@ -1,38 +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" -) - -//Hold the struct need to create a tree -type gEvent struct { - gFile - - //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 gEvent) MarshalJSON() ([]byte, error) { - //Store a ddir copy - type fakeItem gEvent - - //define the item type - type ItemWithType struct { - fakeItem - InternalItemType string - } - - return json.Marshal(ItemWithType{ - (fakeItem)(dir), - "gEvent", - }) -} diff --git a/google/gFile.go b/google/gFile.go deleted file mode 100644 index edcf187..0000000 --- a/google/gFile.go +++ /dev/null @@ -1,33 +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 ( - "time" -) - -type gFile 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 gFile) GetId() string { - return file.Id -} -func (file gFile) GetName() string { - return file.Name -} -func (file gFile) GetDate() *time.Time { - return file.Date -} diff --git a/google/gForm.go b/google/gForm.go deleted file mode 100644 index b839020..0000000 --- a/google/gForm.go +++ /dev/null @@ -1,64 +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" -) - -//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 gForm struct { - gFile - //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 gForm) MarshalJSON() ([]byte, error) { - //Store a ddir copy - type fakeItem gForm - - //define the item type - type ItemWithType struct { - fakeItem - InternalItemType string - } - - return json.Marshal(ItemWithType{ - (fakeItem)(dir), - "gForm", - }) -} diff --git a/migrations/migrations.go b/migrations/migrations.go index 28f5814..c695a9e 100644 --- a/migrations/migrations.go +++ b/migrations/migrations.go @@ -5,6 +5,8 @@ import ( migrate "github.com/rubenv/sql-migrate" ) +type ReferenceType struct{} + func MySql() migrate.MigrationSource { return &migrate.PackrMigrationSource{ Box: packr.New("mysql", "./mysql"), @@ -13,6 +15,6 @@ func MySql() migrate.MigrationSource { func Postgres() migrate.MigrationSource { return &migrate.PackrMigrationSource{ - Box: packr.New("postgres", "./postgres"), + Box: packr.New("postgres", "/postgres"), } } diff --git a/migrations/postgres/3_initial_users.sql b/migrations/postgres/3_initial_users.sql index e162303..df6e10f 100644 --- a/migrations/postgres/3_initial_users.sql +++ b/migrations/postgres/3_initial_users.sql @@ -1,7 +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 userpref (userId int NOT NULL, orgId int NOT NULL, joinDate Date) +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 userpref (userId int NOT NULL, orgId int NOT NULL, joinDate Date); +-- +migrate StatementEnd -- +migrate Down DROP TABLE users; -DROP TABLE userpref; \ No newline at end of file +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 index 1514885..28b34df 100644 --- a/migrations/postgres/4_initial_roles.sql +++ b/migrations/postgres/4_initial_roles.sql @@ -1,5 +1,5 @@ -- +migrate Up -CREATE TABLE IF NOT EXISTS role (id SERIAL PRIMARY KEY, userId int NOT NULL, orgId int NOT NULL, roleId int NOT NULL ) +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_directory.go b/mocks/mock_directory.go deleted file mode 100644 index 0af1118..0000000 --- a/mocks/mock_directory.go +++ /dev/null @@ -1,131 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/reaction-eng/restlib/file (interfaces: Directory) - -// Package mocks is a generated GoMock package. -package mocks - -import ( - gomock "github.com/golang/mock/gomock" - file "github.com/reaction-eng/restlib/file" - reflect "reflect" - time "time" -) - -// MockDirectory is a mock of Directory interface -type MockDirectory struct { - ctrl *gomock.Controller - recorder *MockDirectoryMockRecorder -} - -// MockDirectoryMockRecorder is the mock recorder for MockDirectory -type MockDirectoryMockRecorder struct { - mock *MockDirectory -} - -// NewMockDirectory creates a new mock instance -func NewMockDirectory(ctrl *gomock.Controller) *MockDirectory { - mock := &MockDirectory{ctrl: ctrl} - mock.recorder = &MockDirectoryMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use -func (m *MockDirectory) EXPECT() *MockDirectoryMockRecorder { - return m.recorder -} - -// ForEach mocks base method -func (m *MockDirectory) ForEach(arg0 bool, arg1 file.ItemFunc) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "ForEach", arg0, arg1) -} - -// ForEach indicates an expected call of ForEach -func (mr *MockDirectoryMockRecorder) ForEach(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ForEach", reflect.TypeOf((*MockDirectory)(nil).ForEach), arg0, arg1) -} - -// GetDate mocks base method -func (m *MockDirectory) GetDate() *time.Time { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetDate") - ret0, _ := ret[0].(*time.Time) - return ret0 -} - -// GetDate indicates an expected call of GetDate -func (mr *MockDirectoryMockRecorder) GetDate() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDate", reflect.TypeOf((*MockDirectory)(nil).GetDate)) -} - -// GetId mocks base method -func (m *MockDirectory) GetId() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetId") - ret0, _ := ret[0].(string) - return ret0 -} - -// GetId indicates an expected call of GetId -func (mr *MockDirectoryMockRecorder) GetId() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetId", reflect.TypeOf((*MockDirectory)(nil).GetId)) -} - -// GetItems mocks base method -func (m *MockDirectory) GetItems() []file.Item { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetItems") - ret0, _ := ret[0].([]file.Item) - return ret0 -} - -// GetItems indicates an expected call of GetItems -func (mr *MockDirectoryMockRecorder) GetItems() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetItems", reflect.TypeOf((*MockDirectory)(nil).GetItems)) -} - -// GetName mocks base method -func (m *MockDirectory) GetName() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetName") - ret0, _ := ret[0].(string) - return ret0 -} - -// GetName indicates an expected call of GetName -func (mr *MockDirectoryMockRecorder) GetName() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetName", reflect.TypeOf((*MockDirectory)(nil).GetName)) -} - -// GetParentId mocks base method -func (m *MockDirectory) GetParentId() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetParentId") - ret0, _ := ret[0].(string) - return ret0 -} - -// GetParentId indicates an expected call of GetParentId -func (mr *MockDirectoryMockRecorder) GetParentId() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetParentId", reflect.TypeOf((*MockDirectory)(nil).GetParentId)) -} - -// GetType mocks base method -func (m *MockDirectory) GetType() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetType") - ret0, _ := ret[0].(string) - return ret0 -} - -// GetType indicates an expected call of GetType -func (mr *MockDirectoryMockRecorder) GetType() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetType", reflect.TypeOf((*MockDirectory)(nil).GetType)) -} diff --git a/mocks/mock_document.go b/mocks/mock_document.go deleted file mode 100644 index 5cc9b52..0000000 --- a/mocks/mock_document.go +++ /dev/null @@ -1,132 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/reaction-eng/restlib/file (interfaces: Document) - -// Package mocks is a generated GoMock package. -package mocks - -import ( - gomock "github.com/golang/mock/gomock" - reflect "reflect" - time "time" -) - -// MockDocument is a mock of Document interface -type MockDocument struct { - ctrl *gomock.Controller - recorder *MockDocumentMockRecorder -} - -// MockDocumentMockRecorder is the mock recorder for MockDocument -type MockDocumentMockRecorder struct { - mock *MockDocument -} - -// NewMockDocument creates a new mock instance -func NewMockDocument(ctrl *gomock.Controller) *MockDocument { - mock := &MockDocument{ctrl: ctrl} - mock.recorder = &MockDocumentMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use -func (m *MockDocument) EXPECT() *MockDocumentMockRecorder { - return m.recorder -} - -// GetDate mocks base method -func (m *MockDocument) GetDate() *time.Time { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetDate") - ret0, _ := ret[0].(*time.Time) - return ret0 -} - -// GetDate indicates an expected call of GetDate -func (mr *MockDocumentMockRecorder) GetDate() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDate", reflect.TypeOf((*MockDocument)(nil).GetDate)) -} - -// GetId mocks base method -func (m *MockDocument) GetId() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetId") - ret0, _ := ret[0].(string) - return ret0 -} - -// GetId indicates an expected call of GetId -func (mr *MockDocumentMockRecorder) GetId() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetId", reflect.TypeOf((*MockDocument)(nil).GetId)) -} - -// GetName mocks base method -func (m *MockDocument) GetName() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetName") - ret0, _ := ret[0].(string) - return ret0 -} - -// GetName indicates an expected call of GetName -func (mr *MockDocumentMockRecorder) GetName() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetName", reflect.TypeOf((*MockDocument)(nil).GetName)) -} - -// ParentId mocks base method -func (m *MockDocument) ParentId() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ParentId") - ret0, _ := ret[0].(string) - return ret0 -} - -// ParentId indicates an expected call of ParentId -func (mr *MockDocumentMockRecorder) ParentId() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParentId", reflect.TypeOf((*MockDocument)(nil).ParentId)) -} - -// Preview mocks base method -func (m *MockDocument) Preview() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Preview") - ret0, _ := ret[0].(string) - return ret0 -} - -// Preview indicates an expected call of Preview -func (mr *MockDocumentMockRecorder) Preview() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Preview", reflect.TypeOf((*MockDocument)(nil).Preview)) -} - -// ThumbnailUrl mocks base method -func (m *MockDocument) ThumbnailUrl() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ThumbnailUrl") - ret0, _ := ret[0].(string) - return ret0 -} - -// ThumbnailUrl indicates an expected call of ThumbnailUrl -func (mr *MockDocumentMockRecorder) ThumbnailUrl() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ThumbnailUrl", reflect.TypeOf((*MockDocument)(nil).ThumbnailUrl)) -} - -// Type mocks base method -func (m *MockDocument) Type() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Type") - ret0, _ := ret[0].(string) - return ret0 -} - -// Type indicates an expected call of Type -func (mr *MockDocumentMockRecorder) Type() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Type", reflect.TypeOf((*MockDocument)(nil).Type)) -} diff --git a/mocks/mock_item.go b/mocks/mock_item.go deleted file mode 100644 index 5648271..0000000 --- a/mocks/mock_item.go +++ /dev/null @@ -1,175 +0,0 @@ -// 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 -} - -// BuildFileHierarchy mocks base method -func (m *MockStorage) BuildFileHierarchy(arg0 string, arg1 bool, arg2 func(string) bool) file.Directory { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BuildFileHierarchy", arg0, arg1, arg2) - ret0, _ := ret[0].(file.Directory) - return ret0 -} - -// BuildFileHierarchy indicates an expected call of BuildFileHierarchy -func (mr *MockStorageMockRecorder) BuildFileHierarchy(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildFileHierarchy", reflect.TypeOf((*MockStorage)(nil).BuildFileHierarchy), arg0, arg1, arg2) -} - -// BuildFormHierarchy mocks base method -func (m *MockStorage) BuildFormHierarchy(arg0 string) file.Directory { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BuildFormHierarchy", arg0) - ret0, _ := ret[0].(file.Directory) - return ret0 -} - -// BuildFormHierarchy indicates an expected call of BuildFormHierarchy -func (mr *MockStorageMockRecorder) BuildFormHierarchy(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildFormHierarchy", reflect.TypeOf((*MockStorage)(nil).BuildFormHierarchy), arg0) -} - -// 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{}) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "GetFileAsInterface", arg0, arg1) -} - -// 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) string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetFilePreview", arg0) - ret0, _ := ret[0].(string) - return ret0 -} - -// GetFilePreview indicates an expected call of GetFilePreview -func (mr *MockStorageMockRecorder) GetFilePreview(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFilePreview", reflect.TypeOf((*MockStorage)(nil).GetFilePreview), arg0) -} - -// GetFileThumbnailUrl mocks base method -func (m *MockStorage) GetFileThumbnailUrl(arg0 string) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "GetFileThumbnailUrl", arg0) -} - -// 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_storage.go b/mocks/mock_storage.go index bb6420d..0dd2a59 100644 --- a/mocks/mock_storage.go +++ b/mocks/mock_storage.go @@ -1,76 +1,166 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/reaction-eng/restlib/file (interfaces: Item) +// 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" - time "time" ) -// MockItem is a mock of Item interface -type MockItem struct { +// MockStorage is a mock of Storage interface +type MockStorage struct { ctrl *gomock.Controller - recorder *MockItemMockRecorder + recorder *MockStorageMockRecorder } -// MockItemMockRecorder is the mock recorder for MockItem -type MockItemMockRecorder struct { - mock *MockItem +// MockStorageMockRecorder is the mock recorder for MockStorage +type MockStorageMockRecorder struct { + mock *MockStorage } -// NewMockItem creates a new mock instance -func NewMockItem(ctrl *gomock.Controller) *MockItem { - mock := &MockItem{ctrl: ctrl} - mock.recorder = &MockItemMockRecorder{mock} +// 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 *MockItem) EXPECT() *MockItemMockRecorder { +func (m *MockStorage) EXPECT() *MockStorageMockRecorder { return m.recorder } -// GetDate mocks base method -func (m *MockItem) GetDate() *time.Time { +// 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, "GetDate") - ret0, _ := ret[0].(*time.Time) + 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 } -// GetDate indicates an expected call of GetDate -func (mr *MockItemMockRecorder) GetDate() *gomock.Call { +// 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, "GetDate", reflect.TypeOf((*MockItem)(nil).GetDate)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFileHtml", reflect.TypeOf((*MockStorage)(nil).GetFileHtml), arg0) } -// GetId mocks base method -func (m *MockItem) GetId() string { +// GetFilePreview mocks base method +func (m *MockStorage) GetFilePreview(arg0 string, arg1 int) string { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetId") + ret := m.ctrl.Call(m, "GetFilePreview", arg0, arg1) ret0, _ := ret[0].(string) return ret0 } -// GetId indicates an expected call of GetId -func (mr *MockItemMockRecorder) GetId() *gomock.Call { +// 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, "GetId", reflect.TypeOf((*MockItem)(nil).GetId)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFilePreview", reflect.TypeOf((*MockStorage)(nil).GetFilePreview), arg0, arg1) } -// GetName mocks base method -func (m *MockItem) GetName() string { +// GetFileThumbnailUrl mocks base method +func (m *MockStorage) GetFileThumbnailUrl(arg0 string) string { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetName") + ret := m.ctrl.Call(m, "GetFileThumbnailUrl", arg0) ret0, _ := ret[0].(string) return ret0 } -// GetName indicates an expected call of GetName -func (mr *MockItemMockRecorder) GetName() *gomock.Call { +// 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, "GetName", reflect.TypeOf((*MockItem)(nil).GetName)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PostArbitraryFile", reflect.TypeOf((*MockStorage)(nil).PostArbitraryFile), arg0, arg1, arg2, arg3) } diff --git a/mocks/testRouter.go b/mocks/testRouter.go index 3a17a6d..b055733 100644 --- a/mocks/testRouter.go +++ b/mocks/testRouter.go @@ -3,7 +3,8 @@ package mocks import ( "context" "net/http" - "strings" + + "github.com/gorilla/mux" "github.com/reaction-eng/restlib/users" @@ -13,24 +14,47 @@ import ( ) type TestRouter struct { - routes []routing.Route - userId *int - orgId *int + router *mux.Router + routeMap map[string]routing.Route + userId *int + orgId *int } func NewTestRouter(routerProducer routing.RouteProducer) *TestRouter { - return &TestRouter{ - routerProducer.GetRoutes(), - nil, - nil, - } + return NewTestRouterWithUserId(routerProducer, -1, -1) } func NewTestRouterWithUserId(routerProducer routing.RouteProducer, userId int, orgId int) *TestRouter { - return &TestRouter{ - routerProducer.GetRoutes(), - &userId, - &orgId, + + 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 { @@ -49,15 +73,13 @@ func (router *TestRouter) Handle(w http.ResponseWriter, r *http.Request) *routin r = r.WithContext(ctx) } - uri := r.URL.Path - method := r.Method + router.router.ServeHTTP(w, r) - for _, route := range router.routes { - if strings.EqualFold(route.Pattern, uri) && strings.EqualFold(route.Method, method) { - route.HandlerFunc(w, r) - - return &route - } + var match mux.RouteMatch + if router.router.Match(r, &match) { + route := router.routeMap[match.Route.GetName()] + return &route } + return nil } diff --git a/static/RepoCache.go b/static/RepoCache.go index 7b18192..ce95c3d 100644 --- a/static/RepoCache.go +++ b/static/RepoCache.go @@ -70,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) From ce41cb67aa0d025acc758c8becf003047d4d8f65 Mon Sep 17 00:00:00 2001 From: mcgurn Date: Sat, 6 Jun 2020 16:33:18 -0600 Subject: [PATCH 19/23] bug and test fixes --- configuration/json.go | 8 ++++++++ file/item.go | 2 +- file/listing.go | 2 +- migrations/postgres/3_initial_users.sql | 2 +- users/google.go | 24 ++++++++++++++++++------ 5 files changed, 29 insertions(+), 9 deletions(-) diff --git a/configuration/json.go b/configuration/json.go index 8f05ea4..3953414 100644 --- a/configuration/json.go +++ b/configuration/json.go @@ -19,6 +19,14 @@ type Json struct { 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{ diff --git a/file/item.go b/file/item.go index ca10782..6aa44bd 100644 --- a/file/item.go +++ b/file/item.go @@ -6,7 +6,7 @@ import ( type Item struct { //Keep a boolean if it is a file - Id string `json:"Id"` + Id string `json:"id"` //Hold the item Name string `json:"name"` diff --git a/file/listing.go b/file/listing.go index a7c5bc5..7c3bdfa 100644 --- a/file/listing.go +++ b/file/listing.go @@ -6,7 +6,7 @@ import ( type Listing struct { //Keep a boolean if it is a file - Id string `json:"Id"` + Id string `json:"id"` //Hold the item Name string `json:"name"` diff --git a/migrations/postgres/3_initial_users.sql b/migrations/postgres/3_initial_users.sql index df6e10f..631249e 100644 --- a/migrations/postgres/3_initial_users.sql +++ b/migrations/postgres/3_initial_users.sql @@ -1,6 +1,6 @@ -- +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 userpref (userId int NOT NULL, orgId int NOT NULL, joinDate Date); +CREATE TABLE IF NOT EXISTS userOrganizations (userId int NOT NULL, orgId int NOT NULL, joinDate Date); -- +migrate StatementEnd -- +migrate Down diff --git a/users/google.go b/users/google.go index bda68ef..e38a715 100644 --- a/users/google.go +++ b/users/google.go @@ -79,18 +79,20 @@ Function used to create new user */ func (gHandler *GoogleHandler) handleUserLoginGoogle(w http.ResponseWriter, r *http.Request) { - //Create an empty new user - tok := &oauth2.Token{} + googleLoginStruct := struct { + Token oauth2.Token `json:"token"` + OrganizationId int `json:"organizationId"` + }{} //decode the request body into struct and failed if any error occur - err := json.NewDecoder(r.Body).Decode(&tok) + err := json.NewDecoder(r.Body).Decode(&googleLoginStruct) if err != nil { utils.ReturnJsonError(w, http.StatusUnprocessableEntity, err) return } //Make sure it is valid - if !tok.Valid() { + if !googleLoginStruct.Token.Valid() { utils.ReturnJsonError(w, http.StatusUnprocessableEntity, errors.New("invalid_token")) return @@ -98,7 +100,7 @@ func (gHandler *GoogleHandler) handleUserLoginGoogle(w http.ResponseWriter, r *h //Now get the user info ctx := context.Background() - client := oauth2.NewClient(ctx, gHandler.oAuthConfig.TokenSource(ctx, tok)) + client := oauth2.NewClient(ctx, gHandler.oAuthConfig.TokenSource(ctx, &googleLoginStruct.Token)) svc, err := goauth2.New(client) if err != nil { utils.ReturnJsonError(w, http.StatusUnprocessableEntity, err) @@ -145,6 +147,11 @@ func (gHandler *GoogleHandler) handleUserLoginGoogle(w http.ResponseWriter, r *h //Now get the user by email user, err = gHandler.helper.GetUserByEmail(user.Email()) + // add the user to the + gHandler.helper.AddUserToOrganization(user, googleLoginStruct.OrganizationId) + + user, err = gHandler.helper.GetUserByEmail(user.Email()) + if err != nil { utils.ReturnJsonError(w, http.StatusForbidden, err) } @@ -155,8 +162,13 @@ func (gHandler *GoogleHandler) handleUserLoginGoogle(w http.ResponseWriter, r *h return } + if !InOrganization(user, googleLoginStruct.OrganizationId) { + utils.ReturnJsonError(w, http.StatusForbidden, errors.New("not in organization")) + return + } + //Create JWT token and Store the token in the response - user.SetToken(gHandler.helper.CreateJWTToken(user.Id(), -1, user.Email())) + user.SetToken(gHandler.helper.CreateJWTToken(user.Id(), googleLoginStruct.OrganizationId, user.Email())) //Check to see if the user was created if err == nil { From 9b1ecb93fe14003c2d1a8d87b4a87b5617ce1eb8 Mon Sep 17 00:00:00 2001 From: mcgurn Date: Sat, 6 Jun 2020 18:35:56 -0600 Subject: [PATCH 20/23] add org id to user creation --- users/basicHelper.go | 8 ++++++++ users/basicHelper_test.go | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/users/basicHelper.go b/users/basicHelper.go index 32a330b..60ed0ae 100644 --- a/users/basicHelper.go +++ b/users/basicHelper.go @@ -47,6 +47,14 @@ func (helper *BasicHelper) CreateUser(user User) error { return err } + //Add the users to the org + for _, orgId := range user.Organizations() { + err = helper.AddUserToOrganization(newUser, orgId) + if err != nil { + return err + } + } + //Else issue the request err = helper.IssueActivationRequest(helper.TokenGenerator(), newUser.Id(), newUser.Email()) diff --git a/users/basicHelper_test.go b/users/basicHelper_test.go index 26b4287..3328075 100644 --- a/users/basicHelper_test.go +++ b/users/basicHelper_test.go @@ -44,6 +44,7 @@ func TestBasicHelper_CreateUser(t *testing.T) { issueActivationRequestCount int issueActivationRequestError error tokenGeneratorCount int + addUserToOrgCount int expectedError error }{ { @@ -54,6 +55,7 @@ func TestBasicHelper_CreateUser(t *testing.T) { user.EXPECT().Email().Times(2).Return("user@example.info") user.EXPECT().Password().Times(2).Return("password 123") user.EXPECT().SetPassword("hashed password").Times(1) + user.EXPECT().Organizations().Return([]int{1000, 1002}).Times(1) return user }, validatePasswordCount: 1, @@ -64,6 +66,7 @@ func TestBasicHelper_CreateUser(t *testing.T) { issueActivationRequestCount: 1, issueActivationRequestError: nil, tokenGeneratorCount: 1, + addUserToOrgCount: 2, expectedError: nil, }, { @@ -74,6 +77,7 @@ func TestBasicHelper_CreateUser(t *testing.T) { user.EXPECT().Email().Times(1).Return("user@example.info") user.EXPECT().Password().Times(1).Return("password 123") user.EXPECT().SetPassword("hashed password").Times(0) + user.EXPECT().Organizations().Return([]int{1000, 1002}).Times(0) return user }, validatePasswordCount: 1, @@ -84,6 +88,7 @@ func TestBasicHelper_CreateUser(t *testing.T) { issueActivationRequestCount: 0, issueActivationRequestError: nil, tokenGeneratorCount: 0, + addUserToOrgCount: 0, expectedError: errors.New("invalid password"), }, { @@ -94,6 +99,7 @@ func TestBasicHelper_CreateUser(t *testing.T) { user.EXPECT().Email().Times(1).Return("userexample.info") user.EXPECT().Password().Times(0).Return("password 123") user.EXPECT().SetPassword("hashed password").Times(0) + user.EXPECT().Organizations().Return([]int{1000, 1002}).Times(0) return user }, validatePasswordCount: 0, @@ -104,6 +110,7 @@ func TestBasicHelper_CreateUser(t *testing.T) { issueActivationRequestCount: 0, issueActivationRequestError: nil, tokenGeneratorCount: 0, + addUserToOrgCount: 0, expectedError: errors.New("validate_missing_email"), }, { @@ -114,6 +121,7 @@ func TestBasicHelper_CreateUser(t *testing.T) { user.EXPECT().Email().Times(1).Return("") user.EXPECT().Password().Times(0).Return("password 123") user.EXPECT().SetPassword("hashed password").Times(0) + user.EXPECT().Organizations().Return([]int{1000, 1002}).Times(0) return user }, validatePasswordCount: 0, @@ -124,6 +132,7 @@ func TestBasicHelper_CreateUser(t *testing.T) { issueActivationRequestCount: 0, issueActivationRequestError: nil, tokenGeneratorCount: 0, + addUserToOrgCount: 0, expectedError: errors.New("validate_missing_email"), }, { @@ -134,6 +143,7 @@ func TestBasicHelper_CreateUser(t *testing.T) { user.EXPECT().Email().Times(1).Return(" ") user.EXPECT().Password().Times(0).Return("password 123") user.EXPECT().SetPassword("hashed password").Times(0) + user.EXPECT().Organizations().Return([]int{1000, 1002}).Times(0) return user }, validatePasswordCount: 0, @@ -144,6 +154,7 @@ func TestBasicHelper_CreateUser(t *testing.T) { issueActivationRequestCount: 0, issueActivationRequestError: nil, tokenGeneratorCount: 0, + addUserToOrgCount: 0, expectedError: errors.New("validate_missing_email"), }, { @@ -154,6 +165,7 @@ func TestBasicHelper_CreateUser(t *testing.T) { user.EXPECT().Email().Times(1).Return("user@example.info") user.EXPECT().Password().Times(2).Return("password 123") user.EXPECT().SetPassword("hashed password").Times(1) + user.EXPECT().Organizations().Return([]int{1000, 1002}).Times(0) return user }, validatePasswordCount: 1, @@ -164,6 +176,7 @@ func TestBasicHelper_CreateUser(t *testing.T) { issueActivationRequestCount: 0, issueActivationRequestError: nil, tokenGeneratorCount: 0, + addUserToOrgCount: 0, expectedError: errors.New("user already here"), }, } @@ -174,6 +187,7 @@ func TestBasicHelper_CreateUser(t *testing.T) { mockUserRepo := mocks.NewMockUserRepo(mockCtrl) mockUserRepo.EXPECT().AddUser(user).Times(testCase.addUserCount).Return(user, testCase.addUserError) + mockUserRepo.EXPECT().AddUserToOrganization(user, gomock.Any()).Times(testCase.addUserToOrgCount).Return(nil) mockPasswordResetRepo := mocks.NewMockResetRepo(mockCtrl) token := "token 123" From 800fddf501fd7b5dc68422e195314022d848a55e Mon Sep 17 00:00:00 2001 From: mcgurn Date: Sun, 7 Jun 2020 13:26:21 -0600 Subject: [PATCH 21/23] small bug fixes --- google/drive.go | 3 +-- passwords/basicHelper.go | 2 +- passwords/resetRepoSql.go | 22 ++++++++++++---------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/google/drive.go b/google/drive.go index a5d0f92..598d4c5 100644 --- a/google/drive.go +++ b/google/drive.go @@ -437,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/passwords/basicHelper.go b/passwords/basicHelper.go index 592b9b2..59ce099 100644 --- a/passwords/basicHelper.go +++ b/passwords/basicHelper.go @@ -87,7 +87,7 @@ func (helper *BasicHelper) ComparePasswords(currentPwHash string, testingPasswor * 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) } diff --git a/passwords/resetRepoSql.go b/passwords/resetRepoSql.go index ee53f2d..b7a149b 100644 --- a/passwords/resetRepoSql.go +++ b/passwords/resetRepoSql.go @@ -72,11 +72,12 @@ func NewRepoMySql(db *sql.DB, emailer email.Emailer, configuration configuration //Define a new repo newRepo := ResetRepoSql{ - db: db, - emailer: emailer, - resetEmailConfig: resetEmailConfig, - activationEmailConfig: activationEmailConfig, - tokenLifeSpan: tokenLifeSpan, + db: db, + emailer: emailer, + resetEmailConfig: resetEmailConfig, + activationEmailConfig: activationEmailConfig, + oneTimePasswordEmailConfig: oneTimePasswordEmailConfig, + tokenLifeSpan: tokenLifeSpan, } //Add request data to table @@ -137,11 +138,12 @@ func NewRepoPostgresSql(db *sql.DB, emailer email.Emailer, configuration configu //Define a new repo newRepo := ResetRepoSql{ - db: db, - emailer: emailer, - resetEmailConfig: resetEmailConfig, - activationEmailConfig: activationEmailConfig, - tokenLifeSpan: tokenLifeSpan, + db: db, + emailer: emailer, + resetEmailConfig: resetEmailConfig, + activationEmailConfig: activationEmailConfig, + oneTimePasswordEmailConfig: oneTimePasswordEmailConfig, + tokenLifeSpan: tokenLifeSpan, } //Add request data to table From 29043f35894de223cec05dccba77d09d66ab3d8a Mon Sep 17 00:00:00 2001 From: mcgurn Date: Sun, 7 Jun 2020 15:28:11 -0600 Subject: [PATCH 22/23] correct config --- passwords/resetRepoSql.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/passwords/resetRepoSql.go b/passwords/resetRepoSql.go index b7a149b..ab72fe7 100644 --- a/passwords/resetRepoSql.go +++ b/passwords/resetRepoSql.go @@ -242,7 +242,7 @@ func (repo *ResetRepoSql) IssueOneTimePasswordRequest(token string, userId int, //Make the email header header := email.HeaderInfo{ - Subject: repo.activationEmailConfig.Subject, + Subject: repo.oneTimePasswordEmailConfig.Subject, To: []string{emailAddress}, } From d1eb28710132324ea6a9140d90708f3dc1fceb7e Mon Sep 17 00:00:00 2001 From: mcgurn Date: Sun, 7 Jun 2020 15:57:32 -0600 Subject: [PATCH 23/23] bug fixes on one time password --- users/oneTimePassword.go | 12 ++++++++++++ users/oneTimePassword_test.go | 7 +++++++ 2 files changed, 19 insertions(+) diff --git a/users/oneTimePassword.go b/users/oneTimePassword.go index 68ae7b7..f18d5de 100644 --- a/users/oneTimePassword.go +++ b/users/oneTimePassword.go @@ -89,6 +89,18 @@ func (handler *OneTimePasswordHandler) handleOneTimePasswordGet(w http.ResponseW //Now store it user, err = handler.helper.AddUser(newUser) + if err != nil { + utils.ReturnJsonError(w, http.StatusForbidden, err) + return + } + //Add the users to the org + for _, orgId := range user.Organizations() { + err = handler.helper.AddUserToOrganization(newUser, orgId) + if err != nil { + utils.ReturnJsonError(w, http.StatusForbidden, err) + return + } + } //Make sure it created an id if err != nil { diff --git a/users/oneTimePassword_test.go b/users/oneTimePassword_test.go index d2ae4e0..afb5f59 100644 --- a/users/oneTimePassword_test.go +++ b/users/oneTimePassword_test.go @@ -32,6 +32,7 @@ func TestHandler_handleOneTimePasswordGet(t *testing.T) { issueOneTimePasswordRequestError error addUserCount int addUserError error + addUserToOrgCount int tokenGeneratorCount int expectedStatus int expectedResponse string @@ -55,6 +56,7 @@ func TestHandler_handleOneTimePasswordGet(t *testing.T) { addUserCount: 0, addUserError: nil, tokenGeneratorCount: 1, + addUserToOrgCount: 0, expectedStatus: http.StatusOK, expectedResponse: "{\"message\":\"onetimepassword_token_request_received\",\"status\":true}\n", }, @@ -111,6 +113,7 @@ func TestHandler_handleOneTimePasswordGet(t *testing.T) { user.EXPECT().SetEmail("user@example.com").Times(1) user.EXPECT().SetPassword("").Times(1) user.EXPECT().SetOrganizations(43).Times(1) + user.EXPECT().Organizations().Return([]int{43}).Times(1) user.EXPECT().Id().Times(1).Return(34) user.EXPECT().Email().Times(1).Return("user@example.com") return user @@ -118,6 +121,7 @@ func TestHandler_handleOneTimePasswordGet(t *testing.T) { addUserCount: 1, addUserError: nil, tokenGeneratorCount: 1, + addUserToOrgCount: 1, expectedStatus: http.StatusOK, expectedResponse: "{\"message\":\"onetimepassword_token_request_received\",\"status\":true}\n", }, @@ -134,6 +138,7 @@ func TestHandler_handleOneTimePasswordGet(t *testing.T) { user.EXPECT().SetEmail("user@example.com").Times(1) user.EXPECT().SetPassword("").Times(1) user.EXPECT().SetOrganizations(43).Times(1) + user.EXPECT().Organizations().Return([]int{43}).Times(0) return user }, addUserCount: 1, @@ -141,6 +146,7 @@ func TestHandler_handleOneTimePasswordGet(t *testing.T) { issueOneTimePasswordRequestCount: 0, issueOneTimePasswordRequestError: nil, tokenGeneratorCount: 0, + addUserToOrgCount: 0, expectedStatus: http.StatusForbidden, expectedResponse: "{\"message\":\"db error\",\"status\":false}\n", }, @@ -202,6 +208,7 @@ func TestHandler_handleOneTimePasswordGet(t *testing.T) { 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