Files
kandev/apps/backend/internal/backendapp/turn_adapters_test.go
T
2026-09-23 17:39:51 +01:00

163 lines
4.7 KiB
Go

package backendapp
import (
"context"
"errors"
"testing"
githubsvc "github.com/kandev/kandev/internal/github"
"github.com/kandev/kandev/internal/task/models"
taskrepo "github.com/kandev/kandev/internal/task/repository/sqlite"
)
// fakeSessionMsgRepo is a minimal stub for taskSessionCheckerAdapter.repo
// — returns canned sessions + per-session messages from in-memory maps.
type fakeSessionMsgRepo struct {
sessions map[string][]*models.TaskSession
messages map[string][]*models.Message
sessionsErr error
}
func (r *fakeSessionMsgRepo) ListTaskSessions(_ context.Context, taskID string) ([]*models.TaskSession, error) {
return r.sessions[taskID], r.sessionsErr
}
func TestTaskSessionCheckerWrapsMissingTask(t *testing.T) {
adapter := &taskSessionCheckerAdapter{repo: &fakeSessionMsgRepo{sessionsErr: taskrepo.ErrTaskNotFound}}
_, err := adapter.HasUserAuthoredMessage(context.Background(), "deleted-task")
if !errors.Is(err, githubsvc.ErrTaskNotFound) {
t.Fatalf("HasUserAuthoredMessage error = %v, want github ErrTaskNotFound", err)
}
if !errors.Is(err, taskrepo.ErrTaskNotFound) {
t.Fatalf("wrapped error = %v, want to preserve task repository ErrTaskNotFound", err)
}
}
func (r *fakeSessionMsgRepo) ListMessages(_ context.Context, sessionID string) ([]*models.Message, error) {
return r.messages[sessionID], nil
}
func TestHasUserAuthoredMessage(t *testing.T) {
cases := []struct {
name string
messages []*models.Message
want bool
}{
{
name: "agent-only messages → false",
messages: []*models.Message{
{AuthorType: models.MessageAuthorAgent, Content: "hi"},
},
want: false,
},
{
name: "user message tagged auto_start=true → false (PR/issue/Jira/Linear watch path)",
messages: []*models.Message{
{
AuthorType: models.MessageAuthorUser,
Content: "Review this PR...",
Metadata: map[string]interface{}{"auto_start": true},
},
},
want: false,
},
{
// Regression for claude blocker on b89f880c: recordAutoStartMessage
// (workflow auto-start path) used to tag only workflow_auto_start
// without also setting auto_start, so messages slipped past
// HasUserAuthoredMessage and made the task look user-authored.
// The fix sets both flags; assert this filter recognizes either.
name: "workflow auto-start (auto_start=true, workflow_auto_start=true) → false",
messages: []*models.Message{
{
AuthorType: models.MessageAuthorUser,
Content: "{{task_prompt}}\nDo the thing",
Metadata: map[string]interface{}{
"auto_start": true,
"workflow_auto_start": true,
"plan_mode": true,
},
},
},
want: false,
},
{
// Legacy message from before the cleanup_policy work:
// recordAutoStartMessage only tagged workflow_auto_start. The
// filter must recognize that tag too so installs with piled-up
// pre-upgrade tasks can drain them via the manual button.
name: "legacy workflow auto-start (only workflow_auto_start=true) → false",
messages: []*models.Message{
{
AuthorType: models.MessageAuthorUser,
Content: "legacy auto-start prompt",
Metadata: map[string]interface{}{"workflow_auto_start": true},
},
},
want: false,
},
{
name: "real user message (no auto_start tag) → true",
messages: []*models.Message{
{
AuthorType: models.MessageAuthorUser,
Content: "looks good, ship it",
},
},
want: true,
},
{
name: "auto-start message + later real user message → true",
messages: []*models.Message{
{
AuthorType: models.MessageAuthorUser,
Content: "Review this PR...",
Metadata: map[string]interface{}{"auto_start": true},
},
{
AuthorType: models.MessageAuthorAgent,
Content: "Looking at the diff",
},
{
AuthorType: models.MessageAuthorUser,
Content: "thanks",
},
},
want: true,
},
{
name: "auto_start metadata value of false → still counted as user",
messages: []*models.Message{
{
AuthorType: models.MessageAuthorUser,
Content: "real input",
Metadata: map[string]interface{}{"auto_start": false},
},
},
want: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
adapter := &taskSessionCheckerAdapter{
repo: &fakeSessionMsgRepo{
sessions: map[string][]*models.TaskSession{
"task-1": {{ID: "sess-1"}},
},
messages: map[string][]*models.Message{
"sess-1": tc.messages,
},
},
}
got, err := adapter.HasUserAuthoredMessage(context.Background(), "task-1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != tc.want {
t.Errorf("HasUserAuthoredMessage = %v, want %v", got, tc.want)
}
})
}
}