first commit

This commit is contained in:
2025-09-08 13:27:09 -04:00
commit 41eb06d247
21 changed files with 1471 additions and 0 deletions

86
config/config.go Normal file
View File

@@ -0,0 +1,86 @@
package config
import (
"errors"
"log/slog"
"os"
"gopkg.in/yaml.v3"
)
const (
LoggingOutputJson = "JSON"
LoggingOutputText = "TEXT"
)
type config struct {
Credentials Credentials `yaml:"credentials"`
Directory string `yaml:"directory"`
WatchDebounceSeconds int `yaml:"watch_debounce_seconds"`
LoggingConfig LoggingConfig `yaml:"logging" json:"logging"`
}
type LoggingConfig struct {
Level string
}
func (lc LoggingConfig) ToLogger() *slog.Logger {
var level slog.Level
if err := level.UnmarshalText([]byte(lc.Level)); err != nil {
level = slog.LevelInfo
}
leveler := new(slog.LevelVar)
leveler.Set(level)
return slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: leveler,
}))
}
type Credentials struct {
SecretKey string `yaml:"secret_key"`
TTDApiKey string `yaml:"ttd_api_key"`
}
type Config struct {
Credentials Credentials
Directory string
WatchDebounceSeconds int
Logger *slog.Logger
}
func LoadConfig(filePath string) (*Config, error) {
// Set up slog as the main logger with default JSON handler
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
slog.SetDefault(logger)
data, err := os.ReadFile(filePath)
if err != nil {
slog.Error("Failed to read configuration file", "error", err, "file", filePath)
return nil, err
}
var cfg config
if err := yaml.Unmarshal(data, &cfg); err != nil {
slog.Error("Failed to unmarshal YAML configuration", "error", err)
return nil, err
}
c := new(Config)
c.Logger = cfg.LoggingConfig.ToLogger()
if cfg.Credentials.SecretKey == "" || cfg.Credentials.TTDApiKey == "" {
c.Logger.Error("Missing required credentials in configuration")
return nil, errors.New("missing required credentials in configuration")
}
c.Credentials = cfg.Credentials
c.Directory = cfg.Directory
c.WatchDebounceSeconds = cfg.WatchDebounceSeconds
// Log the selected provider data
slog.Info("Successfully loaded configuration",
"directory", c.Directory,
"watch_debounce_seconds", c.WatchDebounceSeconds,
)
return c, nil
}

108
config/config_test.go Normal file
View File

@@ -0,0 +1,108 @@
package config_test
import (
"os"
"path/filepath"
"strings"
"testing"
"git.savin.nyc/alex/go-iar-notificator/config"
)
func TestLoadConfig_Success(t *testing.T) {
// Create a temporary config file
tempDir := t.TempDir()
configFile := filepath.Join(tempDir, "test_config.yml")
yamlContent := `
credentials:
secret_key: "test_secret"
ttd_api_key: "test_api_key"
directory: "/test/dir"
watch_debounce_seconds: 5
logging:
level: "INFO"
`
err := os.WriteFile(configFile, []byte(yamlContent), 0644)
if err != nil {
t.Fatalf("Failed to create temp config file: %v", err)
}
// Test LoadConfig
cfg, err := config.LoadConfig(configFile)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
// Verify the config
if cfg.Credentials.SecretKey != "test_secret" {
t.Errorf("Expected SecretKey 'test_secret', got %s", cfg.Credentials.SecretKey)
}
if cfg.Credentials.TTDApiKey != "test_api_key" {
t.Errorf("Expected TTDApiKey 'test_api_key', got %s", cfg.Credentials.TTDApiKey)
}
if cfg.Directory != "/test/dir" {
t.Errorf("Expected Directory '/test/dir', got %s", cfg.Directory)
}
if cfg.WatchDebounceSeconds != 5 {
t.Errorf("Expected WatchDebounceSeconds 5, got %d", cfg.WatchDebounceSeconds)
}
if cfg.Logger == nil {
t.Error("Expected Logger to be set, got nil")
}
}
func TestLoadConfig_FileNotFound(t *testing.T) {
_, err := config.LoadConfig("/nonexistent/config.yml")
if err == nil {
t.Error("Expected error for non-existent file, got nil")
}
if !strings.Contains(err.Error(), "no such file") {
t.Errorf("Expected error message to contain 'no such file', got %v", err)
}
}
func TestLoadConfig_InvalidYAML(t *testing.T) {
// Create a temporary file with invalid YAML
tempDir := t.TempDir()
configFile := filepath.Join(tempDir, "invalid_config.yml")
invalidYAML := `
credentials:
secret_key: "test"
invalid yaml here
`
err := os.WriteFile(configFile, []byte(invalidYAML), 0644)
if err != nil {
t.Fatalf("Failed to create temp config file: %v", err)
}
_, err = config.LoadConfig(configFile)
if err == nil {
t.Error("Expected error for invalid YAML, got nil")
}
if !strings.Contains(err.Error(), "yaml") {
t.Errorf("Expected error message to contain 'yaml', got %v", err)
}
}
func TestLoadConfig_MissingCredentials(t *testing.T) {
// Create a temporary config file with missing credentials
tempDir := t.TempDir()
configFile := filepath.Join(tempDir, "missing_creds_config.yml")
yamlContent := `
directory: "/test/dir"
watch_debounce_seconds: 5
logging:
level: "INFO"
`
err := os.WriteFile(configFile, []byte(yamlContent), 0644)
if err != nil {
t.Fatalf("Failed to create temp config file: %v", err)
}
_, err = config.LoadConfig(configFile)
if err == nil {
t.Error("Expected error for missing credentials, got nil")
} else if !strings.Contains(err.Error(), "missing required credentials") {
t.Errorf("Expected error message to contain 'missing required credentials', got %v", err)
}
}