From 25a5b7ecf9ddd31da54e91f87988b77aea857571 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Tue, 3 Feb 2026 15:16:35 -1000 Subject: Add comprehensive test coverage across packages New test files: - api/http_test.go: HTTP client and error handling tests - config/config_test.go: Configuration loading and validation tests - middleware/security_test.go: Security middleware tests - models/atom_test.go: Atom model and conversion tests Expanded test coverage: - api/todoist_test.go: Todoist API client tests - api/trello_test.go: Trello API client tests - auth/auth_test.go: Authentication and CSRF tests - handlers/timeline_logic_test.go: Timeline building logic tests - store/sqlite_test.go: SQLite store operations tests Co-Authored-By: Claude Opus 4.5 --- internal/config/config_test.go | 289 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 internal/config/config_test.go (limited to 'internal/config/config_test.go') diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 0000000..41cd6e0 --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,289 @@ +package config + +import ( + "os" + "testing" + "time" +) + +func TestConfigValidate(t *testing.T) { + tests := []struct { + name string + cfg Config + wantErr bool + }{ + { + name: "valid config", + cfg: Config{ + TodoistAPIKey: "todoist-key", + TrelloAPIKey: "trello-key", + TrelloToken: "trello-token", + }, + wantErr: false, + }, + { + name: "missing todoist key", + cfg: Config{ + TrelloAPIKey: "trello-key", + TrelloToken: "trello-token", + }, + wantErr: true, + }, + { + name: "missing trello key", + cfg: Config{ + TodoistAPIKey: "todoist-key", + TrelloToken: "trello-token", + }, + wantErr: true, + }, + { + name: "missing trello token", + cfg: Config{ + TodoistAPIKey: "todoist-key", + TrelloAPIKey: "trello-key", + }, + wantErr: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.cfg.Validate() + if (err != nil) != tc.wantErr { + t.Errorf("Validate() error = %v, wantErr %v", err, tc.wantErr) + } + }) + } +} + +func TestConfigHasMethods(t *testing.T) { + cfg := Config{ + PlanToEatAPIKey: "pte-key", + TrelloAPIKey: "trello-key", + TrelloToken: "trello-token", + GoogleCredentialsFile: "/path/to/creds.json", + GoogleTasksListID: "@default", + } + + if !cfg.HasPlanToEat() { + t.Error("HasPlanToEat should return true") + } + + if !cfg.HasTrello() { + t.Error("HasTrello should return true") + } + + if !cfg.HasGoogleCalendar() { + t.Error("HasGoogleCalendar should return true") + } + + if !cfg.HasGoogleTasks() { + t.Error("HasGoogleTasks should return true") + } + + // Test with empty config + emptyCfg := Config{} + if emptyCfg.HasPlanToEat() { + t.Error("HasPlanToEat should return false for empty config") + } + if emptyCfg.HasTrello() { + t.Error("HasTrello should return false for empty config") + } + if emptyCfg.HasGoogleCalendar() { + t.Error("HasGoogleCalendar should return false for empty config") + } + if emptyCfg.HasGoogleTasks() { + t.Error("HasGoogleTasks should return false for empty config") + } + + // Test session check + sessionCfg := Config{PlanToEatSession: "session-cookie"} + if !sessionCfg.HasPlanToEat() { + t.Error("HasPlanToEat should return true for session") + } + if !sessionCfg.HasPlanToEatSession() { + t.Error("HasPlanToEatSession should return true") + } +} + +func TestGetEnvWithDefault(t *testing.T) { + // Test with set env var + os.Setenv("TEST_CONFIG_VAR", "test_value") + defer os.Unsetenv("TEST_CONFIG_VAR") + + if val := getEnvWithDefault("TEST_CONFIG_VAR", "default"); val != "test_value" { + t.Errorf("Expected 'test_value', got '%s'", val) + } + + // Test with unset env var + if val := getEnvWithDefault("UNSET_CONFIG_VAR", "default"); val != "default" { + t.Errorf("Expected 'default', got '%s'", val) + } +} + +func TestGetEnvAsInt(t *testing.T) { + // Test with valid int + os.Setenv("TEST_INT_VAR", "42") + defer os.Unsetenv("TEST_INT_VAR") + + if val := getEnvAsInt("TEST_INT_VAR", 10); val != 42 { + t.Errorf("Expected 42, got %d", val) + } + + // Test with invalid int + os.Setenv("TEST_INVALID_INT", "not_a_number") + defer os.Unsetenv("TEST_INVALID_INT") + + if val := getEnvAsInt("TEST_INVALID_INT", 10); val != 10 { + t.Errorf("Expected default 10 for invalid int, got %d", val) + } + + // Test with unset var + if val := getEnvAsInt("UNSET_INT_VAR", 10); val != 10 { + t.Errorf("Expected default 10, got %d", val) + } +} + +func TestGetEnvAsBool(t *testing.T) { + // Test with true values + os.Setenv("TEST_BOOL_TRUE", "true") + defer os.Unsetenv("TEST_BOOL_TRUE") + + if val := getEnvAsBool("TEST_BOOL_TRUE", false); !val { + t.Error("Expected true") + } + + // Test with false values + os.Setenv("TEST_BOOL_FALSE", "false") + defer os.Unsetenv("TEST_BOOL_FALSE") + + if val := getEnvAsBool("TEST_BOOL_FALSE", true); val { + t.Error("Expected false") + } + + // Test with invalid bool + os.Setenv("TEST_INVALID_BOOL", "maybe") + defer os.Unsetenv("TEST_INVALID_BOOL") + + if val := getEnvAsBool("TEST_INVALID_BOOL", true); !val { + t.Error("Expected default true for invalid bool") + } + + // Test with unset var + if val := getEnvAsBool("UNSET_BOOL_VAR", true); !val { + t.Error("Expected default true") + } +} + +// Timezone tests +func TestGetDisplayTimezone(t *testing.T) { + // Before SetDisplayTimezone is called, should return UTC + loc := GetDisplayTimezone() + if loc == nil { + t.Fatal("GetDisplayTimezone should not return nil") + } +} + +func TestNow(t *testing.T) { + now := Now() + // Just verify it returns a valid time + if now.IsZero() { + t.Error("Now() should not return zero time") + } +} + +func TestToday(t *testing.T) { + today := Today() + + // Today should have zero hours, minutes, seconds + if today.Hour() != 0 || today.Minute() != 0 || today.Second() != 0 { + t.Error("Today() should return midnight") + } +} + +func TestParseDateInDisplayTZ(t *testing.T) { + parsed, err := ParseDateInDisplayTZ("2024-01-15") + if err != nil { + t.Fatalf("ParseDateInDisplayTZ failed: %v", err) + } + + if parsed.Year() != 2024 || parsed.Month() != time.January || parsed.Day() != 15 { + t.Errorf("Unexpected date: %v", parsed) + } + + // Test invalid date + _, err = ParseDateInDisplayTZ("invalid") + if err == nil { + t.Error("Expected error for invalid date") + } +} + +func TestParseDateTimeInDisplayTZ(t *testing.T) { + parsed, err := ParseDateTimeInDisplayTZ("2006-01-02 15:04", "2024-01-15 14:30") + if err != nil { + t.Fatalf("ParseDateTimeInDisplayTZ failed: %v", err) + } + + if parsed.Hour() != 14 || parsed.Minute() != 30 { + t.Errorf("Unexpected time: %v", parsed) + } +} + +func TestToDisplayTZ(t *testing.T) { + utcTime := time.Date(2024, 1, 15, 12, 0, 0, 0, time.UTC) + converted := ToDisplayTZ(utcTime) + + // Just verify it doesn't panic and returns a valid time + if converted.IsZero() { + t.Error("ToDisplayTZ should not return zero time") + } +} + +func TestLoad(t *testing.T) { + // Set up required env vars + os.Setenv("TODOIST_API_KEY", "test-todoist-key") + os.Setenv("TRELLO_API_KEY", "test-trello-key") + os.Setenv("TRELLO_TOKEN", "test-trello-token") + os.Setenv("PORT", "9999") + os.Setenv("CACHE_TTL_MINUTES", "10") + os.Setenv("DEBUG", "true") + defer func() { + os.Unsetenv("TODOIST_API_KEY") + os.Unsetenv("TRELLO_API_KEY") + os.Unsetenv("TRELLO_TOKEN") + os.Unsetenv("PORT") + os.Unsetenv("CACHE_TTL_MINUTES") + os.Unsetenv("DEBUG") + }() + + cfg, err := Load() + if err != nil { + t.Fatalf("Load failed: %v", err) + } + + if cfg.TodoistAPIKey != "test-todoist-key" { + t.Errorf("Expected TodoistAPIKey 'test-todoist-key', got '%s'", cfg.TodoistAPIKey) + } + if cfg.Port != "9999" { + t.Errorf("Expected Port '9999', got '%s'", cfg.Port) + } + if cfg.CacheTTLMinutes != 10 { + t.Errorf("Expected CacheTTLMinutes 10, got %d", cfg.CacheTTLMinutes) + } + if !cfg.Debug { + t.Error("Expected Debug to be true") + } +} + +func TestLoad_ValidationError(t *testing.T) { + // Clear required env vars to trigger validation error + os.Unsetenv("TODOIST_API_KEY") + os.Unsetenv("TRELLO_API_KEY") + os.Unsetenv("TRELLO_TOKEN") + + _, err := Load() + if err == nil { + t.Error("Expected validation error when required env vars are missing") + } +} -- cgit v1.2.3