Files

242 lines
8.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package forge_test
import (
"fmt"
"reflect"
"strings"
"testing"
"github.com/cruldra/superpowers-vscode/cli/internal/forge"
"github.com/cruldra/superpowers-vscode/cli/internal/forge/forgetest"
)
// flavors runs fn once against a fake Gitea and once against a fake GitHub, each wired to a matching client.
func flavors(t *testing.T, fn func(t *testing.T, srv *forgetest.Server, f forge.Forge)) {
cases := []struct {
name string
flavor forgetest.Flavor
}{
{"gitea", forgetest.Gitea},
{"github", forgetest.GitHub},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
srv := forgetest.NewServer(t, c.flavor)
fn(t, srv, clientFor(srv))
})
}
}
func clientFor(srv *forgetest.Server) forge.Forge {
if srv.Flavor == forgetest.GitHub {
return forge.NewGitHub(srv.URL, srv.Token, forgetest.Owner, forgetest.Repo)
}
return forge.NewGitea(srv.URL, srv.Token, forgetest.Owner, forgetest.Repo)
}
func TestKindMatchesConstructor(t *testing.T) {
if k := forge.NewGitea("https://g.example.com", "t", "o", "r").Kind(); k != forge.KindGitea {
t.Fatalf("NewGitea().Kind() = %s", k)
}
if k := forge.NewGitHub(forge.GitHubAPIBaseURL, "t", "o", "r").Kind(); k != forge.KindGitHub {
t.Fatalf("NewGitHub().Kind() = %s", k)
}
}
func TestCurrentUserLogin(t *testing.T) {
flavors(t, func(t *testing.T, srv *forgetest.Server, f forge.Forge) {
login, err := f.CurrentUserLogin()
if err != nil || login != "alice" {
t.Fatalf("CurrentUserLogin() = (%q, %v)", login, err)
}
})
}
func TestWrongTokenSurfacesHTTPError(t *testing.T) {
flavors(t, func(t *testing.T, srv *forgetest.Server, _ forge.Forge) {
var f forge.Forge
if srv.Flavor == forgetest.GitHub {
f = forge.NewGitHub(srv.URL, "wrong", forgetest.Owner, forgetest.Repo)
} else {
f = forge.NewGitea(srv.URL, "wrong", forgetest.Owner, forgetest.Repo)
}
_, err := f.CurrentUserLogin()
if err == nil || !strings.Contains(err.Error(), "401") {
t.Fatalf("错误 token 应返回带 401 的错误,得到 %v", err)
}
})
}
func TestCreateGetAndUpdateIssue(t *testing.T) {
flavors(t, func(t *testing.T, srv *forgetest.Server, f forge.Forge) {
created, err := f.CreateIssue("标题", "正文", []string{"alice"})
if err != nil {
t.Fatalf("CreateIssue 出错: %v", err)
}
if created.Number <= 0 || created.HTMLURL == "" {
t.Fatalf("CreateIssue 返回不完整: %+v", created)
}
if err := f.UpdateIssueBody(created.Number, "新正文"); err != nil {
t.Fatalf("UpdateIssueBody 出错: %v", err)
}
got, err := f.GetIssue(created.Number)
if err != nil {
t.Fatalf("GetIssue 出错: %v", err)
}
want := &forge.Issue{
Number: created.Number,
Title: "标题",
Body: "新正文",
State: "open",
Author: "alice",
Assignees: []string{"alice"},
HTMLURL: fmt.Sprintf("%s/acme/widgets/issues/%d", srv.URL, created.Number),
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("GetIssue = %+v\nwant %+v", got, want)
}
})
}
func TestGetIssueWithoutAssigneesGivesEmptyList(t *testing.T) {
flavors(t, func(t *testing.T, srv *forgetest.Server, f forge.Forge) {
n := srv.AddIssue(forgetest.Issue{Title: "t", Author: "bob"})
got, err := f.GetIssue(n)
if err != nil {
t.Fatalf("GetIssue 出错: %v", err)
}
if got.Assignees == nil || len(got.Assignees) != 0 {
t.Fatalf("没有指派人时应是非 nil 空切片,得到 %#v", got.Assignees)
}
})
}
func TestCommentsRoundTrip(t *testing.T) {
flavors(t, func(t *testing.T, srv *forgetest.Server, f forge.Forge) {
n := srv.AddIssue(forgetest.Issue{Title: "t", Author: "bob"})
srv.AddComment(n, "bob", "第一条")
posted, err := f.CreateIssueComment(n, "第二条")
if err != nil {
t.Fatalf("CreateIssueComment 出错: %v", err)
}
if posted.ID == 0 || posted.HTMLURL == "" || posted.Author != "alice" {
t.Fatalf("CreateIssueComment 返回不完整: %+v", posted)
}
list, err := f.ListIssueComments(n)
if err != nil {
t.Fatalf("ListIssueComments 出错: %v", err)
}
if len(list) != 2 || list[0].Body != "第一条" || list[0].Author != "bob" || list[1].Body != "第二条" {
t.Fatalf("评论列表不对: %+v", list)
}
if list[0].CreatedAt == "" {
t.Fatalf("评论应带 created_at: %+v", list[0])
}
})
}
func TestListIssueCommentsReadsEveryPageOnGitHub(t *testing.T) {
srv := forgetest.NewServer(t, forgetest.GitHub)
f := clientFor(srv)
n := srv.AddIssue(forgetest.Issue{Title: "t", Author: "bob"})
for i := 1; i <= 250; i++ {
srv.AddComment(n, "bob", fmt.Sprintf("c%d", i))
}
list, err := f.ListIssueComments(n)
if err != nil {
t.Fatalf("ListIssueComments 出错: %v", err)
}
if len(list) != 250 || list[0].Body != "c1" || list[249].Body != "c250" {
t.Fatalf("应读全 250 条且保持顺序,得到 %d 条", len(list))
}
var commentCalls []string
for _, r := range srv.Requests() {
if strings.Contains(r, "/comments") {
commentCalls = append(commentCalls, r)
}
}
if len(commentCalls) != 3 {
t.Fatalf("250 条应按 per_page=100 分 3 页取,实际请求: %v", commentCalls)
}
if !strings.Contains(commentCalls[0], "per_page=100") {
t.Fatalf("第一页应带 per_page=100: %s", commentCalls[0])
}
}
func TestGetPullRequest(t *testing.T) {
flavors(t, func(t *testing.T, srv *forgetest.Server, f forge.Forge) {
n := srv.AddPull(forgetest.Pull{
Title: "加功能", Body: "说明", State: "closed", Merged: true, Author: "bob",
HeadBranch: "feat/x", HeadSHA: "aaa111", BaseBranch: "main", BaseSHA: "bbb222",
})
got, err := f.GetPullRequest(n)
if err != nil {
t.Fatalf("GetPullRequest 出错: %v", err)
}
if got.Number != n || got.Title != "加功能" || got.Body != "说明" || got.State != "closed" || !got.Merged || got.Author != "bob" {
t.Fatalf("PR 字段不对: %+v", got)
}
if got.Head != (forge.BranchRef{Branch: "feat/x", SHA: "aaa111"}) || got.Base != (forge.BranchRef{Branch: "main", SHA: "bbb222"}) {
t.Fatalf("head/base 不对: %+v / %+v", got.Head, got.Base)
}
if got.HTMLURL == "" {
t.Fatal("PR 应带 html_url")
}
})
}
func TestGetPullRequestReportsHeadBranchAfterItIsDeleted(t *testing.T) {
flavors(t, func(t *testing.T, srv *forgetest.Server, f forge.Forge) {
n := srv.AddPull(forgetest.Pull{
Title: "t", State: "closed", Merged: true, Author: "bob",
HeadBranch: "feat/gone", HeadSHA: "aaa111", BaseBranch: "main", BaseSHA: "bbb222", HeadDeleted: true,
})
got, err := f.GetPullRequest(n)
if err != nil {
t.Fatalf("GetPullRequest 出错: %v", err)
}
if got.Head.Branch != "feat/gone" || got.Base.Branch != "main" {
t.Fatalf("head 分支删掉后仍应报分支名,得到 head=%+v base=%+v", got.Head, got.Base)
}
})
}
func TestCreatePullRequestAndDefaultBranch(t *testing.T) {
flavors(t, func(t *testing.T, srv *forgetest.Server, f forge.Forge) {
srv.DefaultBranch = "trunk"
base, err := f.DefaultBranch()
if err != nil || base != "trunk" {
t.Fatalf("DefaultBranch() = (%q, %v)", base, err)
}
pr, err := f.CreatePullRequest(forge.NewPullRequest{Title: "T", Body: "B", Head: "feat/y", Base: base})
if err != nil {
t.Fatalf("CreatePullRequest 出错: %v", err)
}
stored, ok := srv.Pull(pr.Number)
if !ok || stored.Title != "T" || stored.Body != "B" || stored.HeadBranch != "feat/y" || stored.BaseBranch != "trunk" {
t.Fatalf("服务端收到的 PR 不对: %+v", stored)
}
if pr.HTMLURL == "" || pr.Head.Branch != "feat/y" || pr.Base.Branch != "trunk" {
t.Fatalf("CreatePullRequest 返回不完整: %+v", pr)
}
})
}
func TestPullRequestCommentsUseIssueCommentsEndpoint(t *testing.T) {
flavors(t, func(t *testing.T, srv *forgetest.Server, f forge.Forge) {
n := srv.AddPull(forgetest.Pull{Title: "t", Author: "bob", HeadBranch: "h", BaseBranch: "main"})
if _, err := f.CreateIssueComment(n, "看过了"); err != nil {
t.Fatalf("CreateIssueComment 出错: %v", err)
}
reqs := srv.Requests()
prefix := ""
if srv.Flavor == forgetest.Gitea {
prefix = "/api/v1"
}
want := fmt.Sprintf("POST %s/repos/acme/widgets/issues/%d/comments", prefix, n)
if reqs[len(reqs)-1] != want {
t.Fatalf("PR 评论应走 issues comments 接口,实际 %s,want %s", reqs[len(reqs)-1], want)
}
})
}