diff --git a/apps/backend/internal/github/controller.go b/apps/backend/internal/github/controller.go index fba50387b0..da4b80469f 100644 --- a/apps/backend/internal/github/controller.go +++ b/apps/backend/internal/github/controller.go @@ -787,7 +787,12 @@ func (c *Controller) httpUpdateReviewWatch(ctx *gin.Context) { ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - ctx.JSON(http.StatusOK, gin.H{"updated": true}) + updated, err := c.service.GetReviewWatch(ctx.Request.Context(), id) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + ctx.JSON(http.StatusOK, updated) } func (c *Controller) httpDeleteReviewWatch(ctx *gin.Context) { diff --git a/apps/backend/internal/github/controller_test.go b/apps/backend/internal/github/controller_test.go index 94e02d73d5..9e48ba1408 100644 --- a/apps/backend/internal/github/controller_test.go +++ b/apps/backend/internal/github/controller_test.go @@ -481,6 +481,41 @@ func TestHttpTriggerReviewWatchPublishesNewPREvents(t *testing.T) { } } +func TestHttpUpdateReviewWatchReturnsUpdatedWatch(t *testing.T) { + router, store := setupControllerStoreTest(t) + watch := &ReviewWatch{ + WorkspaceID: "ws-1", + WorkflowID: "wf-1", + WorkflowStepID: "step-1", + Enabled: true, + PollIntervalSeconds: defaultWatchPollIntervalSec, + CleanupPolicy: CleanupPolicyNever, + } + if err := store.CreateReviewWatch(context.Background(), watch); err != nil { + t.Fatalf("create review watch: %v", err) + } + + req := httptest.NewRequest( + http.MethodPut, + "/api/v1/github/watches/review/"+watch.ID+"?workspace_id=ws-1", + strings.NewReader(`{"enabled":false}`), + ) + req.Header.Set("Content-Type", "application/json") + response := httptest.NewRecorder() + router.ServeHTTP(response, req) + + if response.Code != http.StatusOK { + t.Fatalf("status = %d, body = %s", response.Code, response.Body.String()) + } + var updated ReviewWatch + if err := json.NewDecoder(response.Body).Decode(&updated); err != nil { + t.Fatalf("decode update response: %v", err) + } + if updated.ID != watch.ID || updated.Enabled { + t.Fatalf("updated watch = %+v, want id %q and enabled=false", updated, watch.ID) + } +} + func TestHttpListReviewWatchesRequiresWorkspaceForRequests(t *testing.T) { store := newTestStore(t) watch := &ReviewWatch{WorkspaceID: "victim-workspace", Prompt: "secret review prompt"} diff --git a/apps/web/components/integrations/use-watcher-enabled-drafts.test.tsx b/apps/web/components/integrations/use-watcher-enabled-drafts.test.tsx index 6751a24194..656476c877 100644 --- a/apps/web/components/integrations/use-watcher-enabled-drafts.test.tsx +++ b/apps/web/components/integrations/use-watcher-enabled-drafts.test.tsx @@ -1,9 +1,11 @@ import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { useState } from "react"; import { afterEach, describe, expect, it, vi } from "vitest"; import { SettingsSaveProvider } from "@/components/settings/settings-save-provider"; import { useWatcherEnabledDrafts } from "./use-watcher-enabled-drafts"; const watch = { id: "watch-1", enabled: true }; +const saveChangesLabel = "Save changes"; afterEach(cleanup); @@ -23,6 +25,61 @@ function Harness({ save }: { save: (enabled: boolean) => Promise }) { ); } +function ReconciliationHarness({ save }: { save: (enabled: boolean) => Promise }) { + const [items, setItems] = useState([watch]); + const drafts = useWatcherEnabledDrafts({ + id: "test-watches", + items, + saveEnabled: (_watch, enabled) => save(enabled), + }); + return ( + <> + + +