diff options
Diffstat (limited to 'internal/handlers/google_oauth_test.go')
| -rw-r--r-- | internal/handlers/google_oauth_test.go | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/internal/handlers/google_oauth_test.go b/internal/handlers/google_oauth_test.go new file mode 100644 index 0000000..c01bbc4 --- /dev/null +++ b/internal/handlers/google_oauth_test.go @@ -0,0 +1,161 @@ +package handlers + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "golang.org/x/oauth2" + + "task-dashboard/internal/api" +) + +func withSession(t *testing.T, h *Handler, req *http.Request) *http.Request { + t.Helper() + ctx, err := h.sessions.Load(req.Context(), "") + if err != nil { + t.Fatal(err) + } + return req.WithContext(ctx) +} + +func TestHandleGoogleTasksOAuthStart_NotConfigured_Returns503(t *testing.T) { + h, cleanup := setupTestHandler(t) + defer cleanup() + + req := withSession(t, h, httptest.NewRequest("GET", "/settings/google-tasks/connect", nil)) + w := httptest.NewRecorder() + h.HandleGoogleTasksOAuthStart(w, req) + + if w.Code != http.StatusServiceUnavailable { + t.Errorf("status = %d, want 503", w.Code) + } +} + +func TestHandleGoogleTasksOAuthStart_RedirectsToGoogleAndStoresState(t *testing.T) { + h, cleanup := setupTestHandler(t) + defer cleanup() + h.googleTasksOAuthConfig = api.GoogleTasksOAuthConfig("client-id", "client-secret", "https://doot.terst.org/settings/google-tasks/callback") + + req := withSession(t, h, httptest.NewRequest("GET", "/settings/google-tasks/connect", nil)) + w := httptest.NewRecorder() + h.HandleGoogleTasksOAuthStart(w, req) + + if w.Code != http.StatusFound { + t.Fatalf("status = %d, want 302", w.Code) + } + loc := w.Header().Get("Location") + if loc == "" { + t.Fatal("no Location header set") + } + + state := h.sessions.GetString(req.Context(), sessionKeyGoogleOAuthState) + if state == "" { + t.Error("expected a state value to be stored in session") + } +} + +func TestHandleGoogleTasksOAuthCallback_StateMismatch_Returns400(t *testing.T) { + h, cleanup := setupTestHandler(t) + defer cleanup() + h.googleTasksOAuthConfig = api.GoogleTasksOAuthConfig("client-id", "client-secret", "https://doot.terst.org/settings/google-tasks/callback") + + req := httptest.NewRequest("GET", "/settings/google-tasks/callback?state=wrong&code=abc", nil) + req = withSession(t, h, req) + h.sessions.Put(req.Context(), sessionKeyGoogleOAuthState, "expected-state") + + w := httptest.NewRecorder() + h.HandleGoogleTasksOAuthCallback(w, req) + + if w.Code != http.StatusBadRequest { + t.Errorf("status = %d, want 400", w.Code) + } + if tok, _ := h.store.GetOAuthToken(api.GoogleTasksOAuthSource); tok != nil { + t.Error("no token should have been saved on state mismatch") + } +} + +func TestHandleGoogleTasksOAuthCallback_GoogleReturnedError_RedirectsWithError(t *testing.T) { + h, cleanup := setupTestHandler(t) + defer cleanup() + h.googleTasksOAuthConfig = api.GoogleTasksOAuthConfig("client-id", "client-secret", "https://doot.terst.org/settings/google-tasks/callback") + + req := withSession(t, h, httptest.NewRequest("GET", "/settings/google-tasks/callback?error=access_denied", nil)) + w := httptest.NewRecorder() + h.HandleGoogleTasksOAuthCallback(w, req) + + if w.Code != http.StatusFound { + t.Fatalf("status = %d, want 302", w.Code) + } + loc := w.Header().Get("Location") + if loc != "/settings?google_tasks_error=access_denied" { + t.Errorf("Location = %q", loc) + } +} + +func TestHandleGoogleTasksOAuthDisconnect_RemovesStoredToken(t *testing.T) { + h, cleanup := setupTestHandler(t) + defer cleanup() + + if err := h.store.SaveOAuthToken(api.GoogleTasksOAuthSource, &oauth2.Token{AccessToken: "a", RefreshToken: "r"}); err != nil { + t.Fatal(err) + } + + req := withSession(t, h, httptest.NewRequest("POST", "/settings/google-tasks/disconnect", nil)) + w := httptest.NewRecorder() + h.HandleGoogleTasksOAuthDisconnect(w, req) + + if w.Code != http.StatusFound { + t.Fatalf("status = %d, want 302", w.Code) + } + tok, err := h.store.GetOAuthToken(api.GoogleTasksOAuthSource) + if err != nil { + t.Fatal(err) + } + if tok != nil { + t.Error("token should be gone after disconnect") + } +} + +func TestHandleSettingsPage_ReflectsGoogleTasksConnectionState(t *testing.T) { + h, cleanup := setupTestHandler(t) + defer cleanup() + h.googleTasksOAuthConfig = api.GoogleTasksOAuthConfig("client-id", "client-secret", "https://doot.terst.org/settings/google-tasks/callback") + + req := withSession(t, h, httptest.NewRequest("GET", "/settings", nil)) + w := httptest.NewRecorder() + h.HandleSettingsPage(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("status = %d, want 200, body=%s", w.Code, w.Body.String()) + } + mock := h.renderer.(*MockRenderer) + data := mock.Calls[len(mock.Calls)-1].Data + type settingsData struct { + GoogleTasksOAuthReady bool + GoogleTasksConnected bool + } + jsonBytes, _ := json.Marshal(data) + var got settingsData + _ = json.Unmarshal(jsonBytes, &got) + if !got.GoogleTasksOAuthReady { + t.Error("GoogleTasksOAuthReady = false, want true (config was set)") + } + if got.GoogleTasksConnected { + t.Error("GoogleTasksConnected = true, want false (no token saved)") + } + + if err := h.store.SaveOAuthToken(api.GoogleTasksOAuthSource, &oauth2.Token{AccessToken: "a", RefreshToken: "r"}); err != nil { + t.Fatal(err) + } + w2 := httptest.NewRecorder() + h.HandleSettingsPage(w2, req) + data2 := mock.Calls[len(mock.Calls)-1].Data + jsonBytes2, _ := json.Marshal(data2) + var got2 settingsData + _ = json.Unmarshal(jsonBytes2, &got2) + if !got2.GoogleTasksConnected { + t.Error("GoogleTasksConnected = false, want true after saving a token") + } +} |
