mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-16 14:49:09 +02:00
* fix(comments): clarify 409 when a comment-triggered task posts a top-level comment (MUL-4417) A comment-triggered task that posted a parentless top-level comment on its own issue got a 409 whose message named the required parent id but never said top-level comments are disallowed. Agents misread it as the issue being locked and deleted good replies trying to reset. Keep the guard (agents must reply under their trigger comment), but make the error self-explanatory and document the constraint in the CLI --parent help. Add handler-level tests pinning the rejected top-level case and the allowed reply-under-trigger case. Refs GH #5266. Co-authored-by: multica-agent <github@multica.ai> * fix(comments): tighten 409 wording and assert the fix hint (MUL-4417) Review nits on #5292: drop the inaccurate "while it is active" phrasing and the redundancy from the 409 message so it matches the actual allow-set (trigger or coalesced comment); collapse the incident narration to one line; and assert the actionable parent_id (--parent) hint in the regression test so the guidance can't be dropped silently. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: J (Multica agent) <agent-j@multica.ai> Co-authored-by: multica-agent <github@multica.ai>
85 lines
3.0 KiB
Go
85 lines
3.0 KiB
Go
package handler
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestCreateComment_TriggeredTaskRejectsTopLevelComment exercises the full
|
|
// CreateComment handler path (not just taskCoversReplyParent) for the trap
|
|
// reported in MUL-4417 / GH #5266: a comment-triggered task that posts a
|
|
// parentless, top-level comment on its own issue is rejected with a 409 whose
|
|
// message names the trigger comment and states that top-level comments are not
|
|
// allowed. Pinning the message here keeps it from silently drifting away from
|
|
// the behavior the CLI help now documents.
|
|
func TestCreateComment_TriggeredTaskRejectsTopLevelComment(t *testing.T) {
|
|
if testHandler == nil || testPool == nil {
|
|
t.Skip("database not available")
|
|
}
|
|
|
|
fx := newRunningSquadLeaderTaskFixture(t)
|
|
|
|
w := httptest.NewRecorder()
|
|
r := newRequest("POST", "/api/issues/"+fx.IssueID+"/comments", map[string]any{
|
|
"content": "dispatching a squad from this task",
|
|
})
|
|
r = withURLParam(r, "id", fx.IssueID)
|
|
r.Header.Set("X-Agent-ID", fx.LeaderID)
|
|
r.Header.Set("X-Task-ID", fx.TaskID)
|
|
|
|
testHandler.CreateComment(w, r)
|
|
if w.Code != http.StatusConflict {
|
|
t.Fatalf("CreateComment top-level: expected 409, got %d: %s", w.Code, w.Body.String())
|
|
}
|
|
if got := countAgentCommentsForIssue(t, fx.IssueID, fx.LeaderID); got != 0 {
|
|
t.Fatalf("expected rejected top-level comment not to be stored, got %d", got)
|
|
}
|
|
|
|
var body map[string]any
|
|
if err := json.NewDecoder(w.Body).Decode(&body); err != nil {
|
|
t.Fatalf("decode error response: %v", err)
|
|
}
|
|
msg, _ := body["error"].(string)
|
|
// Pin the three semantic pieces without locking the exact wording: why it
|
|
// was rejected, the comment to reply under, and the actionable fix. The last
|
|
// one guards against the guidance being dropped in a future edit.
|
|
for _, want := range []string{
|
|
"top-level comments", // reason
|
|
fx.TriggerCommentID, // the comment to reply under
|
|
"parent_id (--parent)", // actionable fix
|
|
} {
|
|
if !strings.Contains(msg, want) {
|
|
t.Fatalf("409 message should contain %q, got %q", want, msg)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestCreateComment_TriggeredTaskAllowsReplyUnderTrigger is the positive half:
|
|
// the same task replying under its trigger comment succeeds, proving the guard
|
|
// rejects only the top-level case and does not lock the whole issue for
|
|
// comments (MUL-4417 / GH #5266).
|
|
func TestCreateComment_TriggeredTaskAllowsReplyUnderTrigger(t *testing.T) {
|
|
if testHandler == nil || testPool == nil {
|
|
t.Skip("database not available")
|
|
}
|
|
|
|
fx := newRunningSquadLeaderTaskFixture(t)
|
|
|
|
w := httptest.NewRecorder()
|
|
r := newRequest("POST", "/api/issues/"+fx.IssueID+"/comments", map[string]any{
|
|
"content": "replying under the trigger comment",
|
|
"parent_id": fx.TriggerCommentID,
|
|
})
|
|
r = withURLParam(r, "id", fx.IssueID)
|
|
r.Header.Set("X-Agent-ID", fx.LeaderID)
|
|
r.Header.Set("X-Task-ID", fx.TaskID)
|
|
|
|
testHandler.CreateComment(w, r)
|
|
if w.Code != http.StatusCreated {
|
|
t.Fatalf("CreateComment reply-under-trigger: expected 201, got %d: %s", w.Code, w.Body.String())
|
|
}
|
|
}
|