Skip to content

Commit

Permalink
Best version yet
Browse files Browse the repository at this point in the history
  • Loading branch information
tjayrush committed Jan 5, 2025
1 parent 54fdfec commit 6f782fe
Show file tree
Hide file tree
Showing 13 changed files with 214 additions and 235 deletions.
7 changes: 6 additions & 1 deletion app/app.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package app

import (
"log"
"log/slog"
"os"

Expand All @@ -17,7 +18,11 @@ type KhedraApp struct {
}

func NewKhedraApp() *KhedraApp {
cfg := config.MustLoadConfig("config.yaml")
cfg, err := config.LoadConfig()
if err != nil {
log.Fatalf("failed to load config: %v", err)
}

fileLogger, progLogger := types.NewLoggers(cfg.Logging)
cli := initializeCli()

Expand Down
132 changes: 58 additions & 74 deletions pkg/config/config_env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,97 +7,81 @@ import (
"github.com/stretchr/testify/assert"
)

func TestChainMultipleEnvironmentOverrides(t *testing.T) {
env := []string{
"TB_KHEDRA_CHAINS_MAINNET_RPCS=http://rpc1.mainnet,http://rpc2.mainnet",
"TB_KHEDRA_CHAINS_SEPOLIA_ENABLED=true",
}

var configFile string
defer types.SetupTest2(t, env, &configFile)()

cfg := MustLoadConfig(configFile)
assert.Equal(t, []string{"http://rpc1.mainnet", "http://rpc2.mainnet"}, cfg.Chains["mainnet"].RPCs, "RPCs for mainnet should be overridden by environment variable")
assert.True(t, cfg.Chains["sepolia"].Enabled, "Enabled flag for sepolia should be overridden by environment variable")
}

func TestChainEnvironmentVariableOverrides(t *testing.T) {
env := []string{
func TestChainEnvOverrides(t *testing.T) {
defer types.SetupTest([]string{
"TB_KHEDRA_CHAINS_MAINNET_RPCS=http://rpc1.mainnet,http://rpc2.mainnet",
"TB_KHEDRA_CHAINS_MAINNET_ENABLED=false",
"TB_KHEDRA_CHAINS_SEPOLIA_ENABLED=true",
})()

if cfg, err := LoadConfig(); err != nil {
t.Error(err)
} else {
assert.Equal(t, []string{"http://rpc1.mainnet", "http://rpc2.mainnet"}, cfg.Chains["mainnet"].RPCs, "RPCs for mainnet should be overridden by environment variable")
assert.False(t, cfg.Chains["mainnet"].Enabled, "Enabled flag for mainnet should be overridden by environment variable")
assert.True(t, cfg.Chains["sepolia"].Enabled, "Enabled flag for sepolia should be overridden by environment variable")
}

var configFile string
defer types.SetupTest2(t, env, &configFile)()

cfg := MustLoadConfig(configFile)
assert.Equal(t, []string{"http://rpc1.mainnet", "http://rpc2.mainnet"}, cfg.Chains["mainnet"].RPCs, "RPCs for mainnet should be overridden by environment variable")
assert.False(t, cfg.Chains["mainnet"].Enabled, "Enabled flag for mainnet should be overridden by environment variable")
}

func TestChainInvalidBooleanValue(t *testing.T) {
defer types.SetTempEnv("TB_KHEDRA_CHAINS_MAINNET_ENABLED", "not_a_bool")()
defer types.SetupTest(t, nil, types.GetConfigFn, types.EstablishConfig)()

_, err := loadConfig()
assert.Error(t, err, "loadConfig should return an error for invalid boolean value")
assert.Contains(t, err.Error(), "cannot parse", "Error message should indicate the inability to parse the boolean value")
assert.Contains(t, err.Error(), "chains[mainnet].enabled", "Error message should point to the problematic field")
defer types.SetupTest([]string{
"TB_KHEDRA_CHAINS_MAINNET_ENABLED=not_a_bool",
})()

if cfg, err := LoadConfig(); err != nil {
assert.Error(t, err, "loadConfig should return an error for invalid boolean value")
assert.Contains(t, err.Error(), "cannot parse", "Error message should indicate the inability to parse the boolean value")
assert.Contains(t, err.Error(), "chains[mainnet].enabled", "Error message should point to the problematic field")
} else {
t.Error("loadConfig should return an error for invalid boolean value", cfg.Chains["mainnet"].Enabled)
}
}

func TestEnvironmentVariableOverridesForServices(t *testing.T) {
env := []string{
func TestServiceEnvironmentVariableOverrides(t *testing.T) {
defer types.SetupTest([]string{
"TB_KHEDRA_SERVICES_API_ENABLED=false",
"TB_KHEDRA_SERVICES_API_PORT=9090",
})()

if cfg, err := LoadConfig(); err != nil {
t.Error(err)
} else {
apiService, exists := cfg.Services["api"]
assert.True(t, exists, "API service should exist in the configuration")
assert.False(t, apiService.Enabled, "Enabled flag for API service should be overridden by environment variable")
assert.Equal(t, 9090, apiService.Port, "Port for API service should be overridden by environment variable")
}

var configFile string
defer types.SetupTest2(t, env, &configFile)()

cfg := MustLoadConfig(configFile)
apiService, exists := cfg.Services["api"]
assert.True(t, exists, "API service should exist in the configuration")
assert.False(t, apiService.Enabled, "Enabled flag for API service should be overridden by environment variable")
assert.Equal(t, 9090, apiService.Port, "Port for API service should be overridden by environment variable")
}

func TestMultipleServicesEnvironmentOverrides(t *testing.T) {
env := []string{
func TestServiceMultipleEnvironmentOverrides(t *testing.T) {
defer types.SetupTest([]string{
"TB_KHEDRA_SERVICES_API_ENABLED=false",
"TB_KHEDRA_SERVICES_SCRAPER_ENABLED=true",
"TB_KHEDRA_SERVICES_SCRAPER_PORT=8081",
})()

if cfg, err := LoadConfig(); err != nil {
t.Error(err)
} else {
apiService, apiExists := cfg.Services["api"]
scraperService, scraperExists := cfg.Services["scraper"]
assert.True(t, apiExists, "API service should exist in the configuration")
assert.True(t, scraperExists, "Scraper service should exist in the configuration")
assert.False(t, apiService.Enabled, "Enabled flag for API service should be overridden by environment variable")
assert.True(t, scraperService.Enabled, "Enabled flag for Scraper service should be overridden by environment variable")
assert.Equal(t, 8081, scraperService.Port, "Port for Scraper service should be overridden by environment variable")
}

var configFile string
defer types.SetupTest2(t, env, &configFile)()

cfg := MustLoadConfig(configFile)
apiService, apiExists := cfg.Services["api"]
scraperService, scraperExists := cfg.Services["scraper"]
assert.True(t, apiExists, "API service should exist in the configuration")
assert.True(t, scraperExists, "Scraper service should exist in the configuration")
assert.False(t, apiService.Enabled, "Enabled flag for API service should be overridden by environment variable")
assert.True(t, scraperService.Enabled, "Enabled flag for Scraper service should be overridden by environment variable")
assert.Equal(t, 8081, scraperService.Port, "Port for Scraper service should be overridden by environment variable")
}

// func TestNoEnvironmentVariables(t *testing.T) {
// var configFile string
// defer types.SetupTest2(t, []string{}, &configFile)() // types.GetConfigFn, types.EstablishConfig)()

// s := file.AsciiFileToString(configFile)
// fmt.Println(s)
// fmt.Println(configFile)
func TestEnvNoVariables(t *testing.T) {
defer types.SetupTest([]string{})()

// cfg := MustLoadConfig(configFile)
// mainnet := cfg.Chains["mainnet"]
// assert.NotEqual(t, mainnet, nil, "mainnet chain should exist in the configuration")
// for _, chain := range cfg.Chains {
// fmt.Println(chain)
// fmt.Println(chain.RPCs)
// }
// // assert.Equal(t, []string{"http://localhost:8545"}, cfg.Chains["mainnet"].RPCs, "RPCs for mainnet should remain as default")
// // fmt.Println(mainnet)
// // fmt.Println(mainnet.Enabled)
// assert.True(t, mainnet.Enabled, "Enabled flag for mainnet should remain as default")
// }
if cfg, err := LoadConfig(); err != nil {
t.Error(err)
} else {
mainnet := cfg.Chains["mainnet"]
assert.NotEqual(t, mainnet, nil, "mainnet chain should exist in the configuration")
assert.Equal(t, []string{"http://localhost:8545"}, cfg.Chains["mainnet"].RPCs, "RPCs for mainnet should remain as default")
assert.True(t, mainnet.Enabled, "Enabled flag for mainnet should remain as default")
}
}
69 changes: 37 additions & 32 deletions pkg/config/edge_case_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package config

import (
"fmt"
"path/filepath"
"strconv"
"testing"

Expand All @@ -14,29 +13,26 @@ import (
)

func TestServiceInvalidPort(t *testing.T) {
defer types.SetTempEnv("TB_KHEDRA_SERVICES_API_PORT", "invalid_port")()
defer types.SetTempEnv("TEST_MODE", "true")()
defer types.SetupTest([]string{
"TB_KHEDRA_SERVICES_API_PORT=invalid_port",
})()

tmpDir := t.TempDir()
configFile := filepath.Join(tmpDir, "config.yaml")

types.EstablishConfig(configFile)

_, err := loadConfig()
assert.Error(t, err, "loadConfig should return an error for invalid port value")
assert.Contains(t, err.Error(), "invalid_port", "Error message should indicate invalid port")
if cfg, err := LoadConfig(); err != nil {
assert.Error(t, err, "loadConfig should return an error for invalid port value")
assert.Contains(t, err.Error(), "invalid_port", "Error message should indicate invalid port")
} else {
t.Error("loadConfig should return an error for invalid port", cfg.Services["api"])
}
}

func TestChainLargeNumberOf(t *testing.T) {
var configFile string
defer types.SetupTest(t, &configFile, types.GetConfigFn, types.EstablishConfig)()
func TestChainLargeNumberOfChains(t *testing.T) {
defer types.SetupTest([]string{})()

nChains := 1000
cfg := types.NewConfig()
cfg.Chains = make(map[string]types.Chain)
nChains := 1000
for i := 0; i < nChains; i++ {
chainName := "chain" + strconv.Itoa(i)
// fmt.Println(chainName)
cfg.Chains[chainName] = types.Chain{
Name: chainName,
RPCs: []string{fmt.Sprintf("http://%s.rpc", chainName)},
Expand All @@ -45,29 +41,38 @@ func TestChainLargeNumberOf(t *testing.T) {
}

bytes, _ := yaml.Marshal(cfg)
coreFile.StringToAsciiFile(configFile, string(bytes))
coreFile.StringToAsciiFile(types.GetConfigFn(), string(bytes))

// Load the configuration and verify all chains are present (two are there from defaults)
cfg = MustLoadConfig(configFile)
assert.Equal(t, nChains+2, len(cfg.Chains), "All chains should be loaded correctly")
var err error
if cfg, err = LoadConfig(); err != nil {
t.Error(err)
} else {
assert.Equal(t, nChains+2, len(cfg.Chains), "All chains should be loaded correctly")
}
}

func TestChainMissingInConfig(t *testing.T) {
defer types.SetTempEnv("TB_KHEDRA_CHAINS_UNKNOWN_NAME", "unknown")()
defer types.SetTempEnv("TB_KHEDRA_CHAINS_UNKNOWN_RPCS", "http://unknown.rpc")()
defer types.SetTempEnv("TB_KHEDRA_CHAINS_UNKNOWN_ENABLED", "true")()
defer types.SetupTest(t, nil, types.GetConfigFn, types.EstablishConfig)()
defer types.SetupTest([]string{
"TB_KHEDRA_CHAINS_UNKNOWN_NAME=unknown",
"TB_KHEDRA_CHAINS_UNKNOWN_RPCS=http://unknown.rpc",
"TB_KHEDRA_CHAINS_UNKNOWN_ENABLED=true",
})()

_, err := loadConfig()
assert.Error(t, err, "An error should occur if an unknown chain is defined in the environment but not in the configuration file")
if cfg, err := LoadConfig(); err != nil {
assert.Error(t, err, "An error should occur if an unknown chain is defined in the environment but not in the configuration file")
} else {
t.Error("loadConfig should return an error for invalid chain", cfg.Chains["unknown"])
}
}

func TestChainEmptyRPCs(t *testing.T) {
var configFile string
defer types.SetupTest([]string{
"TB_KHEDRA_CHAINS_MAINNET_RPCS=",
})()

defer types.SetTempEnv("TB_KHEDRA_CHAINS_MAINNET_RPCS", "")()
defer types.SetupTest(t, &configFile, types.GetConfigFn, types.EstablishConfig)()

cfg := MustLoadConfig(configFile)
assert.NotEmpty(t, cfg.Chains["mainnet"].RPCs, "Mainnet RPCs should not be empty in the final configuration")
if cfg, err := LoadConfig(); err != nil {
t.Error(err)
} else {
assert.NotEmpty(t, cfg.Chains["mainnet"].RPCs, "Mainnet RPCs should not be empty in the final configuration")
}
}
48 changes: 18 additions & 30 deletions pkg/config/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package config

import (
"fmt"
"log"
"os"
"reflect"
"strconv"
Expand All @@ -17,35 +16,7 @@ import (
"github.com/knadh/koanf/v2"
)

func MustLoadConfig(filename string) types.Config {
var err error
var cfg types.Config
if cfg, err = loadConfig(); err != nil {
log.Fatalf("error loading config: %v", err)
}

// Apply environment variable overrides
for name, service := range cfg.Services {
envEnabled := os.Getenv(fmt.Sprintf("TB_KHEDRA_SERVICES_%s_ENABLED", strings.ToUpper(name)))
if envEnabled != "" {
service.Enabled, _ = strconv.ParseBool(envEnabled)
}

envPort := os.Getenv(fmt.Sprintf("TB_KHEDRA_SERVICES_%s_PORT", strings.ToUpper(name)))
if envPort != "" {
port, err := strconv.Atoi(envPort)
if err == nil {
service.Port = port
}
}

cfg.Services[name] = service
}

return cfg
}

func loadConfig() (types.Config, error) {
func LoadConfig() (types.Config, error) {
var fileK = koanf.New(".")
var envK = koanf.New(".")

Expand Down Expand Up @@ -135,6 +106,23 @@ func loadConfig() (types.Config, error) {
return types.Config{}, err
}

for name, service := range finalCfg.Services {
envEnabled := os.Getenv(fmt.Sprintf("TB_KHEDRA_SERVICES_%s_ENABLED", strings.ToUpper(name)))
if envEnabled != "" {
service.Enabled, _ = strconv.ParseBool(envEnabled)
}

envPort := os.Getenv(fmt.Sprintf("TB_KHEDRA_SERVICES_%s_PORT", strings.ToUpper(name)))
if envPort != "" {
port, err := strconv.Atoi(envPort)
if err == nil {
service.Port = port
}
}

finalCfg.Services[name] = service
}

return finalCfg, nil
}

Expand Down
Loading

0 comments on commit 6f782fe

Please sign in to comment.