mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 19:11:25 +01:00
226 lines
10 KiB
Go
226 lines
10 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dev/tea/internal/clierr"
|
|
"github.com/spf13/cobra"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestParseRefAcceptsNumbersQualifiedNumbersAndURLs(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
raw string
|
|
kind refKind
|
|
want ref
|
|
err string
|
|
}{
|
|
{raw: "12", want: ref{Number: 12}},
|
|
{raw: "#12", want: ref{Number: 12}},
|
|
{raw: " 12 ", want: ref{Number: 12}},
|
|
{raw: "alice/project#12", want: ref{Number: 12, Repo: "alice/project"}},
|
|
{raw: "localhost:3001/alice/project#12", want: ref{Number: 12, Repo: "localhost:3001/alice/project"}},
|
|
{raw: "https://gitea.com/alice/project/issues/3", want: ref{Number: 3, Repo: "https://gitea.com/alice/project"}},
|
|
{raw: "https://gitea.com/alice/project/issues/3#issuecomment-79", want: ref{Number: 3, Repo: "https://gitea.com/alice/project"}},
|
|
{raw: "http://localhost:3001/base/alice/project/pulls/7/files?style=split", kind: pullRef, want: ref{Number: 7, Repo: "http://localhost:3001/base/alice/project"}},
|
|
{raw: "https://gitea.com/alice/issues/issues/4", want: ref{Number: 4, Repo: "https://gitea.com/alice/issues"}},
|
|
{raw: "https://gitea.com/alice/project/pulls/7", kind: issueRef, want: ref{Number: 7, Repo: "https://gitea.com/alice/project"}},
|
|
{raw: "https://gitea.com/alice/project/actions/runs/34", kind: runRef, want: ref{Number: 34, Repo: "https://gitea.com/alice/project"}},
|
|
{raw: "https://gitea.com/alice/project/actions/runs/34/jobs/2", kind: runRef, want: ref{Number: 34, Repo: "https://gitea.com/alice/project"}},
|
|
{raw: "#34", kind: runRef, want: ref{Number: 34}},
|
|
{raw: "0", err: `invalid issue "0": use a number, #number, OWNER/REPO#number or URL`},
|
|
{raw: "#abc", kind: pullRef, err: `invalid pull request "#abc"`},
|
|
{raw: "alice/project#", err: `invalid issue "alice/project#"`},
|
|
{raw: "alice/project#3", kind: runRef, err: `invalid run "alice/project#3": use a run ID or URL`},
|
|
{raw: "https://gitea.com/alice/project", err: `"https://gitea.com/alice/project" is not an issue URL`},
|
|
{raw: "https://gitea.com/alice/project/issues/3", kind: runRef, err: "is not a run URL"},
|
|
{raw: "ftp://gitea.com/alice/project/issues/3", err: "is not an issue URL"},
|
|
} {
|
|
got, err := parseRef(tc.raw, tc.kind)
|
|
if tc.err != "" {
|
|
require.ErrorContains(t, err, tc.err, tc.raw)
|
|
require.Equal(t, clierr.Usage, clierr.ExitCode(err), tc.raw)
|
|
continue
|
|
}
|
|
require.NoError(t, err, tc.raw)
|
|
require.Equal(t, tc.want, got, tc.raw)
|
|
}
|
|
for _, branch := range []string{"feature/login", "bob:fix", "main", "fix#12", "project#12", "bob:fix#3"} {
|
|
_, err := parseRef(branch, pullRef)
|
|
require.ErrorIs(t, err, errNotRef, branch)
|
|
}
|
|
}
|
|
|
|
// refApplication returns an application authenticated against a server listing the open pull
|
|
// requests #3 (feat/one in alice/project), #4 (feat/two), #17 (fix in bob/project) and two
|
|
// pull requests from branch dup. Viewing one pull request finds it open, except the merged #30.
|
|
func refApplication(t *testing.T, gitResults map[string]string) (*application, string) {
|
|
t.Helper()
|
|
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if number, ok := strings.CutPrefix(r.URL.Path, "/api/v1/repos/alice/project/pulls/"); ok {
|
|
state := "open"
|
|
if number == "30" {
|
|
state = "closed"
|
|
}
|
|
_, _ = w.Write([]byte(`{"number":` + number + `,"state":"` + state + `","merged":true}`))
|
|
return
|
|
}
|
|
require.Equal(t, "/api/v1/repos/alice/project/pulls", r.URL.Path)
|
|
writeFirstPage(w, r, `[
|
|
{"number":3,"head":{"ref":"feat/one","repo":{"full_name":"alice/project"}}},
|
|
{"number":4,"head":{"ref":"feat/two","repo":{"full_name":"alice/project"}}},
|
|
{"number":17,"head":{"ref":"fix","repo":{"full_name":"bob/project"}}},
|
|
{"number":20,"head":{"ref":"dup","repo":{"full_name":"alice/project"}}},
|
|
{"number":21,"head":{"ref":"dup","repo":{"full_name":"carol/project"}}}
|
|
]`)
|
|
}))
|
|
t.Cleanup(server.Close)
|
|
deps, _, _ := testDeps(t)
|
|
deps.Getenv = serverEnv(server.URL, map[string]string{"TEA_REPO": "alice/project"})
|
|
results := map[string]string{
|
|
"remote get-url origin": server.URL + "/alice/project.git",
|
|
"remote get-url pulls/bob/project": server.URL + "/bob/project.git",
|
|
"remote get-url fork": server.URL + "/bob/project.git",
|
|
"remote get-url pulls/carol/project": server.URL + "/carol/project.git",
|
|
}
|
|
for key, value := range gitResults {
|
|
results[key] = value
|
|
}
|
|
deps.Git = &fakeGit{results: results}
|
|
return &application{deps: deps}, server.URL
|
|
}
|
|
|
|
func commandContext() *cobra.Command {
|
|
cmd := &cobra.Command{}
|
|
cmd.SetContext(context.Background())
|
|
return cmd
|
|
}
|
|
|
|
func TestPullSelectorDefaultsToTheCheckedOutBranch(t *testing.T) {
|
|
const head = "symbolic-ref --short HEAD"
|
|
branch := func(name string, keys map[string]string) map[string]string {
|
|
results := map[string]string{head: name}
|
|
for key, value := range keys {
|
|
results["config --get branch."+name+"."+key] = value
|
|
}
|
|
return results
|
|
}
|
|
for _, tc := range []struct {
|
|
name string
|
|
git map[string]string
|
|
want int64
|
|
err string
|
|
}{
|
|
{"recorded by pr checkout", branch("pr/17", map[string]string{"tea-pr": "17", "tea-base-remote": "origin", "remote": "pulls/bob/project", "merge": "refs/heads/fix"}), 17, ""},
|
|
{"head branch name kept by pr checkout", branch("fix", map[string]string{"tea-pr": "17", "tea-base-remote": "origin"}), 17, ""},
|
|
{"recorded but merged, the head branch has a newer one", branch("fix", map[string]string{"tea-pr": "30", "tea-base-remote": "origin"}), 17, ""},
|
|
{"recorded but merged, no open one", branch("gone", map[string]string{"tea-pr": "30", "tea-base-remote": "origin"}), 30, ""},
|
|
{"recorded for another repository", branch("pr/9", map[string]string{"tea-pr": "9", "tea-base-remote": "fork", "remote": "fork", "merge": "refs/heads/fix"}), 17, ""},
|
|
{"upstream branch and owner", branch("local", map[string]string{"remote": "origin", "merge": "refs/heads/feat/one"}), 3, ""},
|
|
{"upstream owner tells forks apart", branch("dup", map[string]string{"remote": "pulls/carol/project", "merge": "refs/heads/dup"}), 21, ""},
|
|
{"local branch name", branch("feat/two", nil), 4, ""},
|
|
{"pr/N branch", branch("pr/99", nil), 99, ""},
|
|
{"no pull request", branch("main", nil), 0, `no open pull request for branch "main" in alice/project; pass a number, URL or branch`},
|
|
{"ambiguous branch", branch("dup", nil), 0, `branch "dup" has several open pull requests in alice/project (#20, #21); pass a number`},
|
|
{"detached", map[string]string{}, 0, "not on a branch; pass a pull request number, URL or branch"},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
a, _ := refApplication(t, tc.git)
|
|
_, n, err := a.pullSelector(commandContext(), nil)
|
|
if tc.err != "" {
|
|
require.EqualError(t, err, tc.err)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.want, n)
|
|
})
|
|
}
|
|
a, _ := refApplication(t, branch("feat/two", nil))
|
|
a.options.repo = "alice/project"
|
|
_, _, err := a.pullSelector(commandContext(), nil)
|
|
require.Equal(t, clierr.Usage, clierr.ExitCode(err))
|
|
require.EqualError(t, err, "pass a pull request number, URL or branch with --repo")
|
|
}
|
|
|
|
func TestPullSelectorAcceptsNumbersURLsAndBranches(t *testing.T) {
|
|
a, serverURL := refApplication(t, nil)
|
|
for _, tc := range []struct {
|
|
arg string
|
|
want int64
|
|
err string
|
|
}{
|
|
{"5", 5, ""},
|
|
{"#5", 5, ""},
|
|
{serverURL + "/alice/project/pulls/5", 5, ""},
|
|
{"alice/project#5", 5, ""},
|
|
{"feat/one", 3, ""},
|
|
{"bob:fix", 17, ""},
|
|
{"alice:fix", 0, `no open pull request for branch "alice:fix" in alice/project; pass a number, URL or branch`},
|
|
{"carol:dup", 21, ""},
|
|
{"fix#12", 0, `no open pull request for branch "fix#12" in alice/project`},
|
|
{"dup", 0, "several open pull requests"},
|
|
{serverURL + "/alice/project", 0, "is not a pull request URL"},
|
|
} {
|
|
t.Run(tc.arg, func(t *testing.T) {
|
|
resolved, n, err := a.pullSelector(commandContext(), []string{tc.arg})
|
|
if tc.err != "" {
|
|
require.ErrorContains(t, err, tc.err)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.want, n)
|
|
require.Equal(t, "alice/project", resolved.Repo)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResolveRefSelectsTheReferencedRepository(t *testing.T) {
|
|
a, serverURL := refApplication(t, nil)
|
|
resolved, n, err := a.resolveRef(commandContext(), serverURL+"/bob/tool/issues/8", issueRef)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(8), n)
|
|
require.Equal(t, "bob/tool", resolved.Repo)
|
|
require.Equal(t, serverURL, resolved.Host)
|
|
|
|
resolved, numbers, err := a.resolveRefs(commandContext(), []string{"bob/tool#1", "#2", serverURL + "/bob/tool/issues/3"}, issueRef)
|
|
require.NoError(t, err)
|
|
require.Equal(t, []int64{1, 2, 3}, numbers)
|
|
require.Equal(t, "bob/tool", resolved.Repo)
|
|
|
|
_, _, err = a.resolveRefs(commandContext(), []string{"bob/tool#1", "alice/project#2"}, issueRef)
|
|
require.EqualError(t, err, "bob/tool#1 and alice/project#2 are in different repositories; run one command per repository")
|
|
_, _, err = a.resolveRefs(commandContext(), []string{"1", "#1"}, issueRef)
|
|
require.EqualError(t, err, "#1 is selected more than once; supply unique numbers")
|
|
_, _, err = a.resolveRef(commandContext(), "feature", issueRef)
|
|
require.EqualError(t, err, `invalid issue "feature": use a number, #number, OWNER/REPO#number or URL`)
|
|
|
|
a.options.repo = "BOB/tool"
|
|
resolved, _, err = a.resolveRef(commandContext(), "bob/tool#1", issueRef)
|
|
require.NoError(t, err, "the same repository as --repo")
|
|
require.Equal(t, "bob/tool", resolved.Repo)
|
|
a.options.repo = "bob/other"
|
|
_, _, err = a.resolveRef(commandContext(), serverURL+"/bob/tool/issues/1", issueRef)
|
|
require.Equal(t, clierr.Usage, clierr.ExitCode(err))
|
|
require.EqualError(t, err, serverURL+"/bob/tool/issues/1 points to bob/tool but --repo is bob/other")
|
|
}
|
|
|
|
func TestExpandMeUsesTheLoginTeaActsAs(t *testing.T) {
|
|
alice := target{User: "alice"}
|
|
require.Equal(t, "alice", alice.expandMe("@me"))
|
|
require.Equal(t, "bob", alice.expandMe("bob"))
|
|
assignees := []string{"@me", "carol"}
|
|
alice.expandMeAll(assignees)
|
|
require.Equal(t, []string{"alice", "carol"}, assignees)
|
|
a, _ := refApplication(t, nil)
|
|
a.options.sudo = "bob"
|
|
resolved, err := a.resolve(context.Background(), true)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "bob", resolved.expandMe("@me"))
|
|
}
|