diff --git a/cmd/root.go b/cmd/root.go index f1ad4a76..32e1825d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -30,7 +30,7 @@ var ( func init() { cobra.OnInitialize(initConfig) - rootCmd.PersistentFlags().StringVar(&configPath, "config", "config.yml", "path to config.yml") + rootCmd.PersistentFlags().StringVar(&configPath, "config", "", "path to config.yml") } func initConfig() { @@ -48,7 +48,7 @@ func initConfig() { log = logger.NewLogrus(cfg.LogLevel, cfg.LogFormat) var err error - db, err = storage.OpenGormDB(cfg.Database.Type, cfg.Database.DSN, log) + db, err = storage.OpenGormDB(cfg.DatabaseType, cfg.DatabaseDSN, log) if err != nil { log.WithError(err).Fatal("could not connect to database") } diff --git a/internal/config/config.go b/internal/config/config.go index 88716857..ba7b1f0d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -6,18 +6,21 @@ import ( tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" "github.com/sethvargo/go-password/password" - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" "github.com/spf13/afero" "github.com/spf13/viper" ) +var log = logrus.WithField("package", "config") + type Config struct { - Listen string `mapstructure:"listen"` - LogLevel string `mapstructure:"log_level"` - LogFormat string `mapstructure:"log_format"` - Secret string `mapstructure:"secret"` - Database Database `mapstructure:"database"` - TelegramBotToken string `mapstructure:"telegram_bot_token"` + Listen string `mapstructure:"listen"` + LogLevel string `mapstructure:"log_level"` + LogFormat string `mapstructure:"log_format"` + Secret string `mapstructure:"secret"` + DatabaseType string `mapstructure:"database_type"` + DatabaseDSN string `mapstructure:"database_dsn"` + TelegramBotToken string `mapstructure:"telegram_bot_token"` TelegramBotUser tgbotapi.User MetricsListen string `mapstructure:"metrics_listen"` UploadPath string `mapstructure:"upload_path"` @@ -25,21 +28,20 @@ type Config struct { FileBackend afero.Fs } -type Database struct { - Type string `mapstructure:"type"` - DSN string `mapstructure:"dsn"` -} - // NewConfig returns config with default values. func NewConfig() Config { - secret, _ := password.Generate(64, 12, 12, false, false) + secret, err := password.Generate(64, 12, 12, false, true) + if err != nil { + log.WithError(err).Error("Unable to generate secret") + } return Config{ Listen: ":8080", LogLevel: "debug", LogFormat: "json", Secret: secret, - Database: Database{Type: "sqlite", DSN: "ticker.db"}, + DatabaseType: "sqlite", + DatabaseDSN: "ticker.db", MetricsListen: ":8181", UploadPath: "uploads", UploadURL: "http://localhost:8080", @@ -62,7 +64,8 @@ func LoadConfig(path string) Config { viper.SetDefault("log_level", c.LogLevel) viper.SetDefault("log_format", c.LogFormat) viper.SetDefault("secret", c.Secret) - viper.SetDefault("database", c.Database) + viper.SetDefault("database_type", c.DatabaseType) + viper.SetDefault("database_dsn", c.DatabaseDSN) viper.SetDefault("metrics_listen", c.MetricsListen) viper.SetDefault("telegram_bot_token", "") viper.SetDefault("upload_path", c.UploadPath) @@ -94,7 +97,7 @@ func LoadConfig(path string) Config { err := viper.Unmarshal(&c) if err != nil { - log.WithError(err).Panic("Unable to decode config into struct") + log.WithError(err).Error("Unable to decode config into struct") } return c diff --git a/internal/config/config_test.go b/internal/config/config_test.go index ae4b1c3d..b40ff784 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -4,43 +4,96 @@ import ( "os" "testing" - "github.com/stretchr/testify/assert" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) -func TestLoadConfigWithoutPath(t *testing.T) { - err := os.Setenv("TICKER_LISTEN", ":7070") - if err != nil { - t.Fail() - } +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Config Suite") +} - c := LoadConfig("") - assert.Equal(t, ":7070", c.Listen) +var _ = Describe("Config", func() { + log.Logger.SetOutput(GinkgoWriter) - err = os.Unsetenv("TICKER_LISTEN") - if err != nil { - t.Fail() + var envs map[string]string = map[string]string{ + "TICKER_LISTEN": ":7070", + "TICKER_LOG_LEVEL": "trace", + "TICKER_LOG_FORMAT": "text", + "TICKER_SECRET": "secret", + "TICKER_DATABASE_TYPE": "mysql", + "TICKER_DATABASE_DSN": "user:password@tcp(localhost:3306)/ticker?charset=utf8mb4&parseTime=True&loc=Local", + "TICKER_METRICS_LISTEN": ":9191", + "TICKER_UPLOAD_PATH": "/data/uploads", + "TICKER_UPLOAD_URL": "https://example.com", + "TICKER_TELEGRAM_BOT_TOKEN": "token", } -} -func TestLoadConfigWithPath(t *testing.T) { - c := LoadConfig("../../config.yml") - assert.Equal(t, ":8080", c.Listen) + Describe("LoadConfig", func() { + BeforeEach(func() { + for key := range envs { + os.Unsetenv(key) + } + }) - c = LoadConfig("config.yml") - assert.Equal(t, ":8080", c.Listen) -} + Context("when path is empty", func() { + It("loads config with default values", func() { + c := LoadConfig("") + Expect(c.Listen).To(Equal(":8080")) + Expect(c.LogLevel).To(Equal("debug")) + Expect(c.LogFormat).To(Equal("json")) + Expect(c.Secret).ToNot(BeEmpty()) + Expect(c.DatabaseType).To(Equal("sqlite")) + Expect(c.DatabaseDSN).To(Equal("ticker.db")) + Expect(c.MetricsListen).To(Equal(":8181")) + Expect(c.UploadPath).To(Equal("uploads")) + Expect(c.UploadURL).To(Equal("http://localhost:8080")) + Expect(c.TelegramBotToken).To(BeEmpty()) + Expect(c.TelegramEnabled()).To(BeFalse()) + }) -func TestLoadConfigWithFallback(t *testing.T) { - c := LoadConfig("/x/y/z") - assert.Equal(t, ":8080", c.Listen) -} + It("loads config from env", func() { + for key, value := range envs { + err := os.Setenv(key, value) + Expect(err).ToNot(HaveOccurred()) + } -func TestConfig_TelegramEnabled(t *testing.T) { - c := NewConfig() + c := LoadConfig("") + Expect(c.Listen).To(Equal(envs["TICKER_LISTEN"])) + Expect(c.LogLevel).To(Equal(envs["TICKER_LOG_LEVEL"])) + Expect(c.LogFormat).To(Equal(envs["TICKER_LOG_FORMAT"])) + Expect(c.Secret).To(Equal(envs["TICKER_SECRET"])) + Expect(c.DatabaseType).To(Equal(envs["TICKER_DATABASE_TYPE"])) + Expect(c.DatabaseDSN).To(Equal(envs["TICKER_DATABASE_DSN"])) + Expect(c.MetricsListen).To(Equal(envs["TICKER_METRICS_LISTEN"])) + Expect(c.UploadPath).To(Equal(envs["TICKER_UPLOAD_PATH"])) + Expect(c.UploadURL).To(Equal(envs["TICKER_UPLOAD_URL"])) + Expect(c.TelegramBotToken).To(Equal(envs["TICKER_TELEGRAM_BOT_TOKEN"])) + Expect(c.TelegramEnabled()).To(BeTrue()) + }) + }) - assert.False(t, c.TelegramEnabled()) + Context("when path is not empty", func() { + Context("when path is absolute", func() { + It("loads config from file", func() { + c := LoadConfig("../../config.yml") + Expect(c.Listen).To(Equal(":8080")) + }) + }) - c.TelegramBotToken = "a" + Context("when path is relative", func() { + It("loads config from file", func() { + c := LoadConfig("config.yml") + Expect(c.Listen).To(Equal(":8080")) + }) + }) + }) - assert.True(t, c.TelegramEnabled()) -} + Context("when path is invalid", func() { + It("loads config with default values", func() { + c := LoadConfig("/x/y/z") + Expect(c.Listen).To(Equal(":8080")) + }) + }) + }) +}) diff --git a/internal/storage/sql_storage_test.go b/internal/storage/sql_storage_test.go index 76b3306b..79025e97 100644 --- a/internal/storage/sql_storage_test.go +++ b/internal/storage/sql_storage_test.go @@ -19,6 +19,7 @@ func TestSqlStorage(t *testing.T) { } var _ = Describe("SqlStorage", func() { + log.Logger.SetOutput(GinkgoWriter) db, err := gorm.Open(sqlite.Open("file:testdatabase?mode=memory&cache=shared"), &gorm.Config{}) Expect(err).ToNot(HaveOccurred())