diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/handlers/handlers.go | 6 | ||||
| -rw-r--r-- | internal/handlers/handlers_test.go | 87 |
2 files changed, 70 insertions, 23 deletions
diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 650eeaa..226f117 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -29,10 +29,11 @@ type Handler struct { googleTasksClient api.GoogleTasksAPI config *config.Config renderer Renderer + BuildVersion string } // New creates a new Handler instance -func New(s *store.Store, todoist api.TodoistAPI, trello api.TrelloAPI, planToEat api.PlanToEatAPI, googleCalendar api.GoogleCalendarAPI, googleTasks api.GoogleTasksAPI, cfg *config.Config) *Handler { +func New(s *store.Store, todoist api.TodoistAPI, trello api.TrelloAPI, planToEat api.PlanToEatAPI, googleCalendar api.GoogleCalendarAPI, googleTasks api.GoogleTasksAPI, cfg *config.Config, buildVersion string) *Handler { // Template functions funcMap := template.FuncMap{ "subtract": func(a, b int) int { return a - b }, @@ -59,6 +60,7 @@ func New(s *store.Store, todoist api.TodoistAPI, trello api.TrelloAPI, planToEat googleTasksClient: googleTasks, config: cfg, renderer: NewTemplateRenderer(tmpl), + BuildVersion: buildVersion, } } @@ -96,11 +98,13 @@ func (h *Handler) HandleDashboard(w http.ResponseWriter, r *http.Request) { ActiveTab string CSRFToken string BackgroundURL string + BuildVersion string }{ DashboardData: dashboardData, ActiveTab: tab, CSRFToken: auth.GetCSRFTokenFromContext(ctx), BackgroundURL: backgroundURL, + BuildVersion: h.BuildVersion, } if err := h.renderer.Render(w, "index.html", data); err != nil { diff --git a/internal/handlers/handlers_test.go b/internal/handlers/handlers_test.go index 1842c38..0ad36b2 100644 --- a/internal/handlers/handlers_test.go +++ b/internal/handlers/handlers_test.go @@ -1901,6 +1901,18 @@ func TestHandleTimeline_RendersDataToTemplate(t *testing.T) { } } +// assertTemplateContains reads a template file and asserts it contains the expected string. +func assertTemplateContains(t *testing.T, templatePath, expected, errMsg string) { + t.Helper() + content, err := os.ReadFile(templatePath) + if err != nil { + t.Skipf("Cannot read template file: %v", err) + } + if !strings.Contains(string(content), expected) { + t.Error(errMsg) + } +} + // ============================================================================= // Dashboard Content Verification Tests // ============================================================================= @@ -1945,34 +1957,18 @@ func TestHandleDashboard_ContainsSettingsLink(t *testing.T) { // contains a link to /settings. This catches regressions where the settings // button is accidentally removed. func TestDashboardTemplate_HasSettingsLink(t *testing.T) { - // Read the actual template file and verify it contains a settings link - content, err := os.ReadFile("../../web/templates/index.html") - if err != nil { - t.Skipf("Cannot read template file (running from unexpected directory): %v", err) - } - - templateStr := string(content) - - if !strings.Contains(templateStr, `href="/settings"`) { - t.Error("index.html must contain a link to /settings (settings button missing from UI)") - } + assertTemplateContains(t, "../../web/templates/index.html", + `href="/settings"`, + "index.html must contain a link to /settings (settings button missing from UI)") } // TestSettingsTemplate_HasCSRFToken verifies the settings page exposes a CSRF // token in a meta tag so that JavaScript (e.g. passkey registration) can include // it in POST requests. Without this, passkey registration fails with 403. func TestSettingsTemplate_HasCSRFToken(t *testing.T) { - content, err := os.ReadFile("../../web/templates/settings.html") - if err != nil { - t.Skipf("Cannot read template file: %v", err) - } - tmpl := string(content) - - // The template must include a meta tag or hidden input with the Go template - // variable .CSRFToken so JS can retrieve it - if !strings.Contains(tmpl, ".CSRFToken") { - t.Error("settings.html must expose .CSRFToken (e.g. via meta tag) for JavaScript passkey registration") - } + assertTemplateContains(t, "../../web/templates/settings.html", + ".CSRFToken", + "settings.html must expose .CSRFToken (e.g. via meta tag) for JavaScript passkey registration") } // TestHandleSettingsPage_PassesCSRFToken verifies the settings handler includes @@ -2007,6 +2003,53 @@ func TestHandleSettingsPage_PassesCSRFToken(t *testing.T) { } } +// TestHandleDashboard_PassesBuildVersion verifies the dashboard handler includes +// BuildVersion in the template data. +func TestHandleDashboard_PassesBuildVersion(t *testing.T) { + db, cleanup := setupTestDB(t) + defer cleanup() + + mockTodoist := &mockTodoistClient{} + mockTrello := &mockTrelloClient{} + renderer := NewMockRenderer() + + h := &Handler{ + store: db, + todoistClient: mockTodoist, + trelloClient: mockTrello, + config: &config.Config{CacheTTLMinutes: 5}, + renderer: renderer, + BuildVersion: "abc123", + } + + req := httptest.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + + h.HandleDashboard(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("Expected status 200, got %d", w.Code) + } + + if len(renderer.Calls) == 0 { + t.Fatal("Expected renderer to be called") + } + + lastCall := renderer.Calls[len(renderer.Calls)-1] + dataStr := fmt.Sprintf("%+v", lastCall.Data) + if !strings.Contains(dataStr, "abc123") { + t.Error("Dashboard template data must include BuildVersion value") + } +} + +// TestDashboardTemplate_HasBuildVersion verifies that index.html contains +// a build version placeholder so users can see the deployed version. +func TestDashboardTemplate_HasBuildVersion(t *testing.T) { + assertTemplateContains(t, "../../web/templates/index.html", + ".BuildVersion", + "index.html must contain .BuildVersion to display the build version in the footer") +} + // TestTimelineTemplate_CheckboxesTargetSelf verifies that completion checkboxes // in the timeline calendar grid target their parent item, not #tab-content. func TestTimelineTemplate_CheckboxesTargetSelf(t *testing.T) { |
