summaryrefslogtreecommitdiff
path: root/internal/handlers/handlers_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/handlers/handlers_test.go')
-rw-r--r--internal/handlers/handlers_test.go74
1 files changed, 74 insertions, 0 deletions
diff --git a/internal/handlers/handlers_test.go b/internal/handlers/handlers_test.go
index d338cd3..1842c38 100644
--- a/internal/handlers/handlers_test.go
+++ b/internal/handlers/handlers_test.go
@@ -1958,6 +1958,80 @@ func TestDashboardTemplate_HasSettingsLink(t *testing.T) {
}
}
+// 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")
+ }
+}
+
+// TestHandleSettingsPage_PassesCSRFToken verifies the settings handler includes
+// a CSRFToken in its template data so the passkey registration JS can use it.
+func TestHandleSettingsPage_PassesCSRFToken(t *testing.T) {
+ h, cleanup := setupTestHandler(t)
+ defer cleanup()
+
+ req := httptest.NewRequest("GET", "/settings", nil)
+ w := httptest.NewRecorder()
+
+ h.HandleSettingsPage(w, req)
+
+ if w.Code != http.StatusOK {
+ t.Fatalf("Expected status 200, got %d", w.Code)
+ }
+
+ mock := h.renderer.(*MockRenderer)
+ if len(mock.Calls) == 0 {
+ t.Fatal("Expected renderer to be called")
+ }
+
+ lastCall := mock.Calls[len(mock.Calls)-1]
+ // Use reflection or type assertion to check for CSRFToken field
+ // The data struct should have a CSRFToken field
+ type hasCSRF interface{ GetCSRFToken() string }
+
+ // Check via fmt to inspect the struct fields
+ dataStr := fmt.Sprintf("%+v", lastCall.Data)
+ if !strings.Contains(dataStr, "CSRFToken") {
+ t.Error("Settings page template data must include a CSRFToken field for passkey registration")
+ }
+}
+
+// TestTimelineTemplate_CheckboxesTargetSelf verifies that completion checkboxes
+// in the timeline calendar grid target their parent item, not #tab-content.
+func TestTimelineTemplate_CheckboxesTargetSelf(t *testing.T) {
+ content, err := os.ReadFile("../../web/templates/partials/timeline-tab.html")
+ if err != nil {
+ t.Skipf("Cannot read template file: %v", err)
+ }
+ tmpl := string(content)
+
+ // Find all lines with complete-atom/uncomplete-atom that also reference hx-target
+ lines := strings.Split(tmpl, "\n")
+ for i, line := range lines {
+ if !strings.Contains(line, "complete-atom") {
+ continue
+ }
+ // Look at surrounding lines for the hx-target
+ for j := i; j < len(lines) && j < i+8; j++ {
+ if strings.Contains(lines[j], `hx-target="#tab-content"`) {
+ t.Errorf("Line %d: checkbox targeting #tab-content will replace entire view on complete. "+
+ "Should target closest parent element instead.", j+1)
+ }
+ }
+ }
+}
+
func TestHandleTimeline_WithParams(t *testing.T) {
h, cleanup := setupTestHandler(t)
defer cleanup()