Toggle only a target's active state, so it cannot undo an edit (closes #431)
check / check (push) Successful in 3m26s

The target toggle loaded the target and saved the whole row, so an
edit saved between that load and the save was overwritten with the
old name and settings although it had reported success. The toggle
now updates only the active column.

The entrypoint toggle is unchanged: an entrypoint has no edit form,
so the toggle is the only thing that changes one after creation.

Model: opus-5-5
This commit is contained in:
2026-10-02 11:43:27 +00:00
parent 5551f75251
commit f1716ec435
2 changed files with 72 additions and 2 deletions
+6 -2
View File
@@ -1814,9 +1814,13 @@ func (h *Handlers) HandleTargetToggle() http.HandlerFunc {
return false, err return false, err
} }
tgt.Active = !tgt.Active // Only the active column: saving the whole row would
// write back the name and settings read above over an
// edit saved since.
active := !tgt.Active
return tgt.Active, h.db.DB().Save(&tgt).Error return active, h.db.DB().Model(&tgt).
Update("active", active).Error
}, },
"failed to toggle target", "failed to toggle target",
targetActivated, targetDeactivated, targetActivated, targetDeactivated,
+66
View File
@@ -0,0 +1,66 @@
package handlers_test
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/gorm"
)
// TestHandleTargetToggle_DoesNotUndoAnEdit proves that a toggle which
// loaded the target before an edit of it was saved does not write the
// old name and settings back over the edit. The edit is submitted from
// a callback on the toggle's own read of the target, so it is saved
// after that read and before the toggle writes.
func TestHandleTargetToggle_DoesNotUndoAnEdit(t *testing.T) {
t.Parallel()
env := setupSourceTest(t)
wh, tgt := seedHTTPTarget(t, env, "", "")
require.True(t, tgt.Active)
var (
edited bool
editCode int
)
require.NoError(t, env.db.DB().Callback().Query().
After("gorm:query").
Register("test:edit_after_toggle_read", func(tx *gorm.DB) {
// The edit reads the target too; only the toggle's read,
// the first, submits it.
if tx.Statement.Table != "targets" || edited {
return
}
edited = true
editCode = submitTargetEdit(
env, wh.ID, tgt.ID,
editForm(editReplacedURL, "", ""),
).Code
}),
)
req := postRequest(
"/hook/"+wh.ID+"/targets/"+tgt.ID+"/toggle",
env.cookies,
map[string]string{paramSourceID: wh.ID, paramTargetID: tgt.ID},
)
w := httptest.NewRecorder()
env.handlers.HandleTargetToggle().ServeHTTP(w, req)
require.Equal(t, http.StatusSeeOther, w.Code)
require.Equal(t, http.StatusSeeOther, editCode)
stored := storedTarget(t, env, tgt.ID)
assert.False(t, stored.Active)
assert.Equal(t, "edited-name", stored.Name)
assert.Equal(t, 5, stored.MaxRetries)
assert.Equal(
t, editReplacedURL, storedHTTPConfig(t, env, tgt.ID).URL,
)
}