Files
gitea-tea/internal/app/clone_test.go
T
bircni ea13c9e4f5 refactor: remove features beyond the UX plan
Drop additions that the UX plan does not ask for:

- the interactive label edit form; label edit now requires a flag
- label clone and the skip mode of label creation it needed
- repo rename rewriting the Git remote or tea.repository; it now
  warns that the remote still points to the old name
- the extra "source" remote on clones of pull mirrors
- run artifact download, which duplicates run download --zip
- 256-color and 24-bit label swatches; colors stay ANSI 16
- config list --show-source (config get keeps it)
- the membership and access GETs before team member/repo add and
  remove; Gitea's PUT and DELETE are idempotent

label edit/delete keep --by-name: it is the only way to address a
label whose name is a number that is also another label's ID.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-12 10:36:50 +02:00

174 lines
8.8 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package app
import (
"context"
"fmt"
"net/http"
"net/url"
"path/filepath"
"strings"
"testing"
"gitea.dev/tea/internal/git"
"gitea.dev/tea/internal/settings"
"github.com/stretchr/testify/require"
)
func TestCloneValidatesRemoteBeforeGitAndReturnsJSON(t *testing.T) {
remote := ""
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, "/api/v1/repos/alice/project", r.URL.Path)
_, _ = fmt.Fprintf(w, `{"id":7,"full_name":"alice/project","clone_url":%q}`, remote)
}))
defer server.Close()
deps, out, diagnostics := serverDeps(t, server.URL)
prefix := credentialCall(t, deps, server.URL, "alice") + " "
runner := &fakeGit{results: map[string]string{prefix + "clone -- " + server.URL + "/alice/project.git destination": ""}}
deps.Git = runner
args := []string{"repo", "clone", "alice/project", "destination", "--no-input", "--json=id,full_name"}
parsed, err := url.Parse(server.URL)
require.NoError(t, err)
for _, invalid := range []string{"ssh://git@" + parsed.Hostname() + "/alice/project.git", "https://unrelated.example.org/alice/project.git", server.URL + "/bob/project.git", server.URL + "/alice/different.git"} {
remote = invalid
require.ErrorContains(t, Execute(context.Background(), deps, args), "clone URL does not match")
require.Empty(t, runner.calls)
require.Empty(t, out.String())
}
remote = server.URL + "/alice/project.git"
require.NoError(t, Execute(context.Background(), deps, args))
require.Equal(t, []string{prefix + "clone -- " + remote + " destination"}, runner.calls)
require.False(t, runner.prompt)
require.JSONEq(t, `{"id":7,"full_name":"alice/project"}`, out.String())
require.Empty(t, diagnostics.String())
out.Reset()
runner.calls = nil
runner.results[prefix+"clone --depth 2 -- "+remote+" destination"] = ""
require.NoError(t, Execute(context.Background(), deps, append(args, "--depth", "2")))
require.Equal(t, []string{prefix + "clone --depth 2 -- " + remote + " destination"}, runner.calls)
runner.calls = nil
require.ErrorContains(t, Execute(context.Background(), deps, append(args, "--depth", "-1")), "--depth must be nonnegative")
require.Empty(t, runner.calls)
}
func TestSSHCloneUsesSelectedAccountKeyWithoutPersistingIt(t *testing.T) {
remote := ""
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = fmt.Fprintf(w, `{"id":7,"full_name":"alice/project","ssh_url":%q}`, remote)
}))
defer server.Close()
parsed, err := url.Parse(server.URL)
require.NoError(t, err)
remote = "git@" + parsed.Hostname() + ":alice/project.git"
deps, _, _ := serverDeps(t, server.URL)
configureHost(t, deps, server.URL, settings.Host{User: "alice", GitProtocol: "ssh", Users: map[string]settings.Account{
"alice": {AuthMethod: "token", CredentialStore: "file", SSHKey: "/keys/alice key"},
"bob": {AuthMethod: "token", CredentialStore: "file", SSHKey: "/keys/bob"},
}})
sshArgs, err := git.SSHArguments("/keys/alice key", false)
require.NoError(t, err)
call := strings.Join(append(sshArgs, "clone", "--", remote, "destination"), " ")
runner := &fakeGit{results: map[string]string{call: ""}}
deps.Git = runner
require.NoError(t, Execute(context.Background(), deps, []string{"repo", "clone", "alice/project", "destination", "--no-input"}))
require.Equal(t, []string{call}, runner.calls)
require.False(t, runner.prompt)
require.NoError(t, Execute(context.Background(), deps, []string{"config", "set", "ssh_agent", "true", "--host", server.URL, "--user", "alice"}))
runner.calls = nil
call = "clone -- " + remote + " destination"
runner.results[call] = ""
require.NoError(t, Execute(context.Background(), deps, []string{"repo", "clone", "alice/project", "destination", "--no-input"}))
require.Equal(t, []string{call}, runner.calls)
}
func TestCloneAddsUpstreamRemoteForForks(t *testing.T) {
remote, parent := "", ""
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, "/api/v1/repos/bob/project", r.URL.Path)
_, _ = fmt.Fprintf(w, `{"id":8,"name":"project","full_name":"bob/project","fork":true,"clone_url":%q,"parent":{"id":7,"full_name":"alice/project","default_branch":"main","clone_url":%q}}`, remote, parent)
}))
defer server.Close()
deps, out, _ := serverDeps(t, server.URL)
prefix := credentialCall(t, deps, server.URL, "alice") + " "
remote = server.URL + "/bob/project.git"
parent = server.URL + "/alice/project.git"
runner := &fakeGit{results: map[string]string{
prefix + "clone -- " + remote + " destination": "",
"remote add -- upstream " + parent: "",
"config branch.main.remote upstream": "",
"config branch.main.merge refs/heads/main": "",
"config branch.main.pushRemote origin": "",
}}
deps.Git = runner
cwd := t.TempDir()
require.NoError(t, Execute(context.Background(), deps, []string{"repo", "clone", "bob/project", "destination", "--cwd", cwd, "--no-input", "--json=id,full_name,fork"}))
require.Equal(t, []string{
prefix + "clone -- " + remote + " destination",
"remote add -- upstream " + parent,
"config branch.main.remote upstream",
"config branch.main.merge refs/heads/main",
"config branch.main.pushRemote origin",
}, runner.calls)
clone := filepath.Join(cwd, "destination")
require.Equal(t, []string{cwd, clone, clone, clone, clone}, runner.dirs)
require.JSONEq(t, `{"id":8,"full_name":"bob/project","fork":true}`, out.String())
}
func TestSSHCloneTrustsAPIEndpointUnlessSSHHostIsConfigured(t *testing.T) {
remote := "ssh://git@ssh.example.test:2222/alice/project.git"
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = fmt.Fprintf(w, `{"id":7,"full_name":"alice/project","ssh_url":%q}`, remote)
}))
defer server.Close()
deps, _, _ := serverDeps(t, server.URL)
configure := func(sshHost string) {
configureHost(t, deps, server.URL, settings.Host{User: "alice", GitProtocol: "ssh", SSHHost: sshHost, Users: tokenAccounts(settings.StoreFile, "alice")})
}
sshArgs, err := git.SSHArguments("", false)
require.NoError(t, err)
call := strings.Join(append(sshArgs, "clone", "--", remote, "destination"), " ")
runner := &fakeGit{results: map[string]string{call: ""}}
deps.Git = runner
args := []string{"repo", "clone", "alice/project", "destination", "--no-input"}
configure("")
require.NoError(t, Execute(context.Background(), deps, args))
require.Equal(t, []string{call}, runner.calls)
runner.calls = nil
configure("git.example.test")
require.ErrorContains(t, Execute(context.Background(), deps, args), "clone URL does not match")
require.Empty(t, runner.calls)
configure("ssh.example.test:2222")
require.NoError(t, Execute(context.Background(), deps, args))
require.Equal(t, []string{call}, runner.calls)
runner.calls = nil
remote = "ssh://git@ssh.example.test:2222/alice/other.git"
configure("")
require.ErrorContains(t, Execute(context.Background(), deps, args), "clone URL does not match")
require.Empty(t, runner.calls)
}
func TestClonePassesGitFlagsAndNamesTheUpstreamRemote(t *testing.T) {
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprintf(w, `{"name":"project","full_name":"bob/project","fork":true,"clone_url":"http://%[1]s/bob/project.git","parent":{"full_name":"alice/project","default_branch":"main","clone_url":"http://%[1]s/alice/project.git"}}`, r.Host)
}))
defer server.Close()
deps, _, diagnostics := repoTTYDeps(t, server.URL)
clone := credentialCall(t, deps, server.URL, "alice", "clone", "--branch", "dev", "--", server.URL+"/bob/project.git")
parent := server.URL + "/alice/project.git"
runner := &fakeGit{results: map[string]string{
clone: "", "remote add -- parent " + parent: "", "config branch.main.remote parent": "",
"config branch.main.merge refs/heads/main": "", "config branch.main.pushRemote origin": "",
}}
deps.Git = runner
require.NoError(t, Execute(context.Background(), deps, []string{"repo", "clone", "bob/project", "-u", "parent", "--", "--branch", "dev"}))
require.Equal(t, []string{clone, "remote add -- parent " + parent, "config branch.main.remote parent", "config branch.main.merge refs/heads/main", "config branch.main.pushRemote origin"}, runner.calls)
require.Equal(t, "✓ Cloned bob/project into project\n✓ Added remote parent for alice/project; tea commands in the clone target it (change with tea repo set-default)\n", diagnostics.String())
runner.calls = nil
require.NoError(t, Execute(context.Background(), deps, []string{"repo", "clone", "bob/project", "--no-upstream", "--", "--branch", "dev"}))
require.Equal(t, []string{clone}, runner.calls)
require.ErrorContains(t, Execute(context.Background(), deps, []string{"repo", "clone", "bob/project", "a", "b"}), "too many arguments")
}