Files
kandev/apps/backend/internal/backendapp/gateway_git_association_test.go
T

133 lines
6.7 KiB
Go

package backendapp
import (
"context"
"errors"
"testing"
"time"
)
func TestIsAbandonedTurnCompletion(t *testing.T) {
startedAt := time.Date(2026, time.July, 24, 10, 0, 0, 0, time.UTC)
if !isAbandonedTurnCompletion(map[string]interface{}{"started_at": startedAt, "completed_at": &startedAt}) {
t.Fatal("an orphan turn completed at its start time must not notify")
}
completedAt := startedAt.Add(time.Second)
if isAbandonedTurnCompletion(map[string]interface{}{"started_at": startedAt, "completed_at": &completedAt}) {
t.Fatal("a live turn completion must remain eligible for notifications")
}
}
func TestClarificationNotificationOccurrence(t *testing.T) {
tests := []struct {
name string
data map[string]interface{}
wantTaskID string
wantSessionID string
wantPendingID string
wantEligible bool
}{
{
name: "structured clarification requesting input",
data: map[string]interface{}{
"type": "clarification_request", "requests_input": true, "author_type": "agent",
"task_id": "task-1", "session_id": "session-1", "metadata": map[string]interface{}{"pending_id": "pending-1"},
},
wantTaskID: "task-1", wantSessionID: "session-1", wantPendingID: "pending-1", wantEligible: true,
},
{name: "ordinary message", data: map[string]interface{}{"type": "message", "requests_input": true}, wantEligible: false},
{name: "clarification without input request", data: map[string]interface{}{"type": "clarification_request", "requests_input": false}, wantEligible: false},
{name: "missing metadata", data: map[string]interface{}{"type": "clarification_request", "requests_input": true}, wantEligible: false},
{name: "wrong metadata type", data: map[string]interface{}{"type": "clarification_request", "requests_input": true, "metadata": "pending-1"}, wantEligible: false},
{name: "empty pending ID", data: map[string]interface{}{"type": "clarification_request", "requests_input": true, "metadata": map[string]interface{}{"pending_id": ""}}, wantEligible: false},
{name: "user authored clarification", data: map[string]interface{}{"type": "clarification_request", "requests_input": true, "author_type": "user", "task_id": "task-1", "session_id": "session-1", "metadata": map[string]interface{}{"pending_id": "pending-1"}}, wantEligible: false},
{name: "system authored clarification", data: map[string]interface{}{"type": "clarification_request", "requests_input": true, "author_type": "system", "task_id": "task-1", "session_id": "session-1", "metadata": map[string]interface{}{"pending_id": "pending-1"}}, wantEligible: false},
{name: "missing task ID", data: map[string]interface{}{"type": "clarification_request", "requests_input": true, "author_type": "agent", "session_id": "session-1", "metadata": map[string]interface{}{"pending_id": "pending-1"}}, wantEligible: false},
{name: "missing session ID", data: map[string]interface{}{"type": "clarification_request", "requests_input": true, "author_type": "agent", "task_id": "task-1", "metadata": map[string]interface{}{"pending_id": "pending-1"}}, wantEligible: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
taskID, sessionID, pendingID, eligible := clarificationNotificationOccurrence(tt.data)
if taskID != tt.wantTaskID || sessionID != tt.wantSessionID || pendingID != tt.wantPendingID || eligible != tt.wantEligible {
t.Fatalf("occurrence = (%q, %q, %q, %v), want (%q, %q, %q, %v)", taskID, sessionID, pendingID, eligible, tt.wantTaskID, tt.wantSessionID, tt.wantPendingID, tt.wantEligible)
}
})
}
}
func TestCreatedChangeAssociationRouterRoutesGitLabSingleAndMultiRepo(t *testing.T) {
var got []string
router := createdChangeAssociationRouter{
resolveRepositoryID: func(_ context.Context, _ string, repo string) string {
if repo == "" {
return "repo-primary"
}
return "repo-" + repo
},
resolveWorkspaceID: func(context.Context, string) (string, error) { return "workspace-1", nil },
associateGitLab: func(_ context.Context, workspaceID, sessionID, taskID, repositoryID, mrURL string) error {
got = append(got, workspaceID+"|"+sessionID+"|"+taskID+"|"+repositoryID+"|"+mrURL)
return nil
},
}
for _, repo := range []string{"", "secondary"} {
if err := router.associate(context.Background(), "session-1", "task-1", "gitlab", "https://gitlab.example/g/r/-/merge_requests/4", "feature", repo); err != nil {
t.Fatalf("associate repo %q: %v", repo, err)
}
}
want := []string{
"workspace-1|session-1|task-1|repo-primary|https://gitlab.example/g/r/-/merge_requests/4",
"workspace-1|session-1|task-1|repo-secondary|https://gitlab.example/g/r/-/merge_requests/4",
}
if len(got) != len(want) || got[0] != want[0] || got[1] != want[1] {
t.Fatalf("associations = %#v", got)
}
}
func TestCreatedChangeAssociationRouterPreservesGitHubAndMissingServices(t *testing.T) {
var got string
router := createdChangeAssociationRouter{
resolveRepositoryID: func(context.Context, string, string) string { return "repo-1" },
resolveWorkspaceID: func(context.Context, string) (string, error) { return "workspace-1", nil },
associateGitHub: func(
_ context.Context, workspaceID, sessionID, taskID, repositoryID, prURL, branch string,
) error {
got = workspaceID + "|" + sessionID + "|" + taskID + "|" + repositoryID + "|" + prURL + "|" + branch
return nil
},
}
if err := router.associate(context.Background(), "session-1", "task-1", "github", "https://github.com/g/r/pull/2", "feature", ""); err != nil {
t.Fatal(err)
}
if got != "workspace-1|session-1|task-1|repo-1|https://github.com/g/r/pull/2|feature" {
t.Fatalf("GitHub association = %q", got)
}
if err := router.associate(context.Background(), "session-1", "task-1", "gitlab", "url", "feature", ""); err != nil {
t.Fatalf("missing GitLab service should be a no-op: %v", err)
}
}
func TestCreatedChangeAssociationRouterGitLabFailureCanRetry(t *testing.T) {
attempts := 0
router := createdChangeAssociationRouter{
resolveRepositoryID: func(context.Context, string, string) string { return "repo-1" },
resolveWorkspaceID: func(context.Context, string) (string, error) { return "workspace-1", nil },
associateGitLab: func(context.Context, string, string, string, string, string) error {
attempts++
if attempts == 1 {
return errors.New("temporary failure containing sensitive provider output")
}
return nil
},
}
if err := router.associate(context.Background(), "session-1", "task-1", "gitlab", "url", "feature", ""); err == nil {
t.Fatal("first association unexpectedly succeeded")
}
if err := router.associate(context.Background(), "session-1", "task-1", "gitlab", "url", "feature", ""); err != nil {
t.Fatalf("retry: %v", err)
}
if attempts != 2 {
t.Fatalf("attempts = %d", attempts)
}
}