Replace all exec.Command("gh", ...) calls with the existing pkg/forge
wrapper around the Forgejo Go SDK. BugSETI no longer requires the gh
CLI to be installed.
Changes:
- fetcher: use forge.ListIssues/GetIssue instead of gh issue list/view
- submit: use forge.ForkRepo/CreatePullRequest instead of gh pr create
- seeder: use git clone with forge URL + token auth instead of gh clone
- ghcheck: CheckForge() returns *forge.Client via forge.NewFromConfig()
- config: add ForgeURL/ForgeToken fields (GitHubToken kept for migration)
- pkg/forge: add Token(), GetCurrentUser(), ForkRepo(), CreatePullRequest(),
ListIssueComments(), and label filtering to ListIssuesOpts
Co-Authored-By: Virgil <virgil@lethean.io>
234 lines
5.5 KiB
Go
234 lines
5.5 KiB
Go
package bugseti
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func testSubmitService(t *testing.T) *SubmitService {
|
|
t.Helper()
|
|
cfg := testConfigService(t, nil, nil)
|
|
notify := &NotifyService{enabled: false, config: cfg}
|
|
stats := &StatsService{
|
|
config: cfg,
|
|
stats: &Stats{
|
|
ReposContributed: make(map[string]*RepoStats),
|
|
DailyActivity: make(map[string]*DayStats),
|
|
},
|
|
}
|
|
return NewSubmitService(cfg, notify, stats, nil)
|
|
}
|
|
|
|
// --- NewSubmitService / ServiceName ---
|
|
|
|
func TestNewSubmitService_Good(t *testing.T) {
|
|
s := testSubmitService(t)
|
|
if s == nil {
|
|
t.Fatal("expected non-nil SubmitService")
|
|
}
|
|
if s.config == nil || s.notify == nil || s.stats == nil {
|
|
t.Fatal("expected all dependencies set")
|
|
}
|
|
}
|
|
|
|
func TestServiceName_Good(t *testing.T) {
|
|
s := testSubmitService(t)
|
|
if got := s.ServiceName(); got != "SubmitService" {
|
|
t.Fatalf("expected %q, got %q", "SubmitService", got)
|
|
}
|
|
}
|
|
|
|
// --- Submit validation ---
|
|
|
|
func TestSubmit_Bad_NilSubmission(t *testing.T) {
|
|
s := testSubmitService(t)
|
|
_, err := s.Submit(nil)
|
|
if err == nil {
|
|
t.Fatal("expected error for nil submission")
|
|
}
|
|
if !strings.Contains(err.Error(), "invalid submission") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSubmit_Bad_NilIssue(t *testing.T) {
|
|
s := testSubmitService(t)
|
|
_, err := s.Submit(&PRSubmission{Issue: nil})
|
|
if err == nil {
|
|
t.Fatal("expected error for nil issue")
|
|
}
|
|
if !strings.Contains(err.Error(), "invalid submission") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSubmit_Bad_EmptyWorkDir(t *testing.T) {
|
|
s := testSubmitService(t)
|
|
_, err := s.Submit(&PRSubmission{
|
|
Issue: &Issue{Number: 1, Repo: "owner/repo", Title: "test"},
|
|
WorkDir: "",
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected error for empty work directory")
|
|
}
|
|
if !strings.Contains(err.Error(), "work directory not specified") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
// --- generatePRBody ---
|
|
|
|
func TestGeneratePRBody_Good_Basic(t *testing.T) {
|
|
s := testSubmitService(t)
|
|
issue := &Issue{Number: 42, Repo: "owner/repo", Title: "A bug"}
|
|
body := s.generatePRBody(issue)
|
|
|
|
if !strings.Contains(body, "#42") {
|
|
t.Fatal("PR body should reference issue number")
|
|
}
|
|
if !strings.Contains(body, "## Summary") {
|
|
t.Fatal("PR body should have Summary section")
|
|
}
|
|
if !strings.Contains(body, "## Changes") {
|
|
t.Fatal("PR body should have Changes section")
|
|
}
|
|
if !strings.Contains(body, "## Testing") {
|
|
t.Fatal("PR body should have Testing section")
|
|
}
|
|
if !strings.Contains(body, "BugSETI") {
|
|
t.Fatal("PR body should have BugSETI attribution")
|
|
}
|
|
}
|
|
|
|
func TestGeneratePRBody_Good_WithContext(t *testing.T) {
|
|
s := testSubmitService(t)
|
|
issue := &Issue{
|
|
Number: 7,
|
|
Repo: "owner/repo",
|
|
Title: "Fix login",
|
|
Context: &IssueContext{
|
|
Summary: "The login endpoint returns 500 on empty password.",
|
|
},
|
|
}
|
|
body := s.generatePRBody(issue)
|
|
|
|
if !strings.Contains(body, "## Context") {
|
|
t.Fatal("PR body should have Context section when context exists")
|
|
}
|
|
if !strings.Contains(body, "login endpoint returns 500") {
|
|
t.Fatal("PR body should include context summary")
|
|
}
|
|
}
|
|
|
|
func TestGeneratePRBody_Good_WithoutContext(t *testing.T) {
|
|
s := testSubmitService(t)
|
|
issue := &Issue{Number: 7, Repo: "owner/repo", Title: "Fix login"}
|
|
body := s.generatePRBody(issue)
|
|
|
|
if strings.Contains(body, "## Context") {
|
|
t.Fatal("PR body should omit Context section when no context")
|
|
}
|
|
}
|
|
|
|
func TestGeneratePRBody_Good_EmptyContextSummary(t *testing.T) {
|
|
s := testSubmitService(t)
|
|
issue := &Issue{
|
|
Number: 7,
|
|
Repo: "owner/repo",
|
|
Title: "Fix login",
|
|
Context: &IssueContext{Summary: ""},
|
|
}
|
|
body := s.generatePRBody(issue)
|
|
|
|
if strings.Contains(body, "## Context") {
|
|
t.Fatal("PR body should omit Context section when summary is empty")
|
|
}
|
|
}
|
|
|
|
// --- PRSubmission / PRResult struct tests ---
|
|
|
|
func TestPRSubmission_Good_Defaults(t *testing.T) {
|
|
sub := &PRSubmission{
|
|
Issue: &Issue{Number: 10, Repo: "o/r"},
|
|
WorkDir: "/tmp/work",
|
|
}
|
|
if sub.Branch != "" {
|
|
t.Fatal("expected empty branch to be default")
|
|
}
|
|
if sub.Title != "" {
|
|
t.Fatal("expected empty title to be default")
|
|
}
|
|
if sub.CommitMsg != "" {
|
|
t.Fatal("expected empty commit msg to be default")
|
|
}
|
|
}
|
|
|
|
func TestPRResult_Good_Success(t *testing.T) {
|
|
r := &PRResult{
|
|
Success: true,
|
|
PRURL: "https://forge.lthn.ai/o/r/pulls/1",
|
|
PRNumber: 1,
|
|
ForkOwner: "me",
|
|
}
|
|
if !r.Success {
|
|
t.Fatal("expected success")
|
|
}
|
|
if r.Error != "" {
|
|
t.Fatal("expected no error on success")
|
|
}
|
|
}
|
|
|
|
func TestPRResult_Good_Failure(t *testing.T) {
|
|
r := &PRResult{
|
|
Success: false,
|
|
Error: "fork failed: something",
|
|
}
|
|
if r.Success {
|
|
t.Fatal("expected failure")
|
|
}
|
|
if r.Error == "" {
|
|
t.Fatal("expected error message")
|
|
}
|
|
}
|
|
|
|
// --- PRStatus struct ---
|
|
|
|
func TestPRStatus_Good(t *testing.T) {
|
|
s := &PRStatus{
|
|
State: "open",
|
|
Mergeable: true,
|
|
CIPassing: true,
|
|
Approved: false,
|
|
}
|
|
if s.State != "open" {
|
|
t.Fatalf("expected open, got %s", s.State)
|
|
}
|
|
if !s.Mergeable {
|
|
t.Fatal("expected mergeable")
|
|
}
|
|
if s.Approved {
|
|
t.Fatal("expected not approved")
|
|
}
|
|
}
|
|
|
|
// --- splitRepo ---
|
|
|
|
func TestSplitRepo_Good(t *testing.T) {
|
|
owner, repo, err := splitRepo("myorg/myrepo")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if owner != "myorg" || repo != "myrepo" {
|
|
t.Fatalf("expected myorg/myrepo, got %s/%s", owner, repo)
|
|
}
|
|
}
|
|
|
|
func TestSplitRepo_Bad(t *testing.T) {
|
|
_, _, err := splitRepo("invalidrepo")
|
|
if err == nil {
|
|
t.Fatal("expected error for invalid repo format")
|
|
}
|
|
if !strings.Contains(err.Error(), "invalid repo format") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|