package github import ( "context" "testing" "time" ) // TestAssociateExistingPRByURL_CreatesTwoRowsForDifferentTasks regression-tests // the bug where two tasks created from the same PR via the GitHub page "+ // Task" launcher ended up with only one github_task_prs row. The launcher // now calls AssociateExistingPRByURL directly so the linkage no longer // depends on branch-based discovery (which fails for review tasks that use // synthetic worktree branches). func TestAssociateExistingPRByURL_CreatesTwoRowsForDifferentTasks(t *testing.T) { _, svc, mockClient, _ := setupPollerTest(t) ctx := context.Background() mockClient.AddPR(&PR{ Number: 42, Title: "Feature PR", State: "open", HeadSHA: "abc", HeadBranch: "feat/x", RepoOwner: "org", RepoName: "repo", HTMLURL: "https://github.com/org/repo/pull/42", }) prURL := "https://github.com/org/repo/pull/42" tp1, err := svc.AssociateExistingPRByURL(ctx, "task-A", "repo-1", prURL) if err != nil { t.Fatalf("first associate: %v", err) } if tp1 == nil || tp1.TaskID != "task-A" || tp1.PRNumber != 42 { t.Fatalf("unexpected first TaskPR: %+v", tp1) } tp2, err := svc.AssociateExistingPRByURL(ctx, "task-B", "repo-1", prURL) if err != nil { t.Fatalf("second associate: %v", err) } if tp2 == nil || tp2.TaskID != "task-B" || tp2.PRNumber != 42 { t.Fatalf("unexpected second TaskPR: %+v", tp2) } if tp1.ID == tp2.ID { t.Fatalf("expected distinct rows for distinct tasks, got duplicate id %s", tp1.ID) } } func TestAssociateExistingPRByURL_RejectsBadURL(t *testing.T) { _, svc, _, _ := setupPollerTest(t) ctx := context.Background() if _, err := svc.AssociateExistingPRByURL(ctx, "t", "r", "not-a-pr-url"); err == nil { t.Fatal("expected error for malformed PR URL") } } // TestAssociateExistingPRByURL_LinkingAlreadyMergedPRPopulatesOutcomeFields // is the regression for AC-36 on the "+Task" URL link path: this flow // fetches via GetPR, a full single-PR fetch (AC-10), so a PR that is already // merged at the moment of linking must not create a row with merged_at set // and merged_by_login left NULL — that combination is exactly the // writer-fault state AC-36 forbids. func TestAssociateExistingPRByURL_LinkingAlreadyMergedPRPopulatesOutcomeFields(t *testing.T) { _, svc, mockClient, _ := setupPollerTest(t) ctx := context.Background() mergedAt := time.Now().UTC() mockClient.AddPR(&PR{ Number: 43, Title: "Already merged", State: prStateMerged, HeadSHA: "abc", HeadBranch: "feat/y", RepoOwner: "org", RepoName: "repo", HTMLURL: "https://github.com/org/repo/pull/43", MergedAt: &mergedAt, Draft: false, IsDraftObserved: true, ChangedFiles: 7, ChangedFilesObserved: true, MergedByLogin: "carlosflorencio", }) tp, err := svc.AssociateExistingPRByURL(ctx, "task-C", "repo-1", "https://github.com/org/repo/pull/43") if err != nil { t.Fatalf("associate: %v", err) } if tp == nil || tp.MergedAt == nil { t.Fatalf("unexpected TaskPR: %+v", tp) } if tp.MergedByLogin == nil || *tp.MergedByLogin != "carlosflorencio" { t.Errorf("merged_by_login = %v, want %q (AC-36 requires non-NULL once merged_at is set)", tp.MergedByLogin, "carlosflorencio") } if tp.IsDraft == nil || *tp.IsDraft != false { t.Errorf("is_draft = %v, want false observed, not NULL", tp.IsDraft) } if tp.ChangedFiles == nil || *tp.ChangedFiles != 7 { t.Errorf("changed_files = %v, want 7 observed, not NULL", tp.ChangedFiles) } } // TestAssociateExistingPRByURL_LinkingAutoMergeArmedPRSetsLatch is the // regression for COR-001 / codex[P1]: associatePRWithTask's fresh-insert // branch only captured resolveTaskPROutcomeFields' first three return // values (is_draft, changed_files, merged_by_login) via `_, _` for the rest, // discarding closed_by_login and auto_merge_observed_at. Linking a PR that // already has auto-merge armed at the moment of the "+Task" URL link (a // populating fetch, AC-10) must set the auto_merge_observed_at latch // immediately (AC-16) rather than leaving it NULL until some later sync // happens to observe the same state again. func TestAssociateExistingPRByURL_LinkingAutoMergeArmedPRSetsLatch(t *testing.T) { _, svc, mockClient, _ := setupPollerTest(t) ctx := context.Background() mockClient.AddPR(&PR{ Number: 44, Title: "Auto-merge armed", State: "open", HeadSHA: "abc", HeadBranch: "feat/z", RepoOwner: "org", RepoName: "repo", HTMLURL: "https://github.com/org/repo/pull/44", Draft: false, ChangedFiles: 3, AutoMergeEnabled: true, }) tp, err := svc.AssociateExistingPRByURL(ctx, "task-D", "repo-1", "https://github.com/org/repo/pull/44") if err != nil { t.Fatalf("associate: %v", err) } if tp == nil || tp.AutoMergeObservedAt == nil { t.Fatalf("AutoMergeObservedAt = %v, want set on a fresh insert observing auto-merge armed (AC-16)", tp.AutoMergeObservedAt) } }