mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 11:01:25 +01:00
revert: tea 1.0 rewrite (#1122–#1126) (#1146)
Reverts #1126, #1125, #1124, #1123 and #1122. `go.mod` keeps the Go 1.26 line with toolchain `go1.26.9` (instead of the original `go1.26.6`) so the security fix from #1131 isn't lost. reopens #69 reopens #141 reopens #162 reopens #172 reopens #197 reopens #232 reopens #240 reopens #246 reopens #323 reopens #325 reopens #326 reopens #328 reopens #344 reopens #351 reopens #373 reopens #403 reopens #406 reopens #417 reopens #430 reopens #457 reopens #480 reopens #482 reopens #489 reopens #490 reopens #498 reopens #500 reopens #504 reopens #510 reopens #516 reopens #523 reopens #556 reopens #560 reopens #569 reopens #571 reopens #602 reopens #632 reopens #633 reopens #643 reopens #666 reopens #670 reopens #672 reopens #677 reopens #683 reopens #697 reopens #726 reopens #737 reopens #770 reopens #779 reopens #804 reopens #817 reopens #831 reopens #833 reopens #834 reopens #908 reopens #966 reopens #1023 reopens #1029 reopens #1032 reopens #1050 reopens #1052 reopens #1069 reopens #1073 reopens #1087 reopens #1091 reopens #1094 reopens #1110 reopens #1115 reopens #1116 reopens #1118 Reviewed-on: https://gitea.com/gitea/tea/pulls/1146 Reviewed-by: TheFox0x7 <95654+thefox0x7@noreply.gitea.com>
This commit is contained in:
1 parent
33bb12729e
commit
c5f674111a
604 files changed
+33711
-35545
No files matched your search
@@ -0,0 +1,139 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
gitea "gitea.dev/sdk"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
teacmd "gitea.dev/tea/cmd"
|
||||
)
|
||||
|
||||
func runAdminCommand(t *testing.T, args []string) error {
|
||||
t.Helper()
|
||||
|
||||
adminCmd := teacmd.CmdAdmin
|
||||
return adminCmd.Run(t.Context(), args)
|
||||
}
|
||||
|
||||
func createAdminTestUser(t *testing.T, client *gitea.Client, username, password string) {
|
||||
t.Helper()
|
||||
|
||||
mustChangePassword := false
|
||||
user, _, err := client.Admin.CreateUser(t.Context(), gitea.CreateUserOption{
|
||||
LoginName: username,
|
||||
Username: username,
|
||||
Email: username + "@example.com",
|
||||
Password: password,
|
||||
MustChangePassword: &mustChangePassword,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, username, user.UserName)
|
||||
|
||||
t.Cleanup(func() {
|
||||
if _, err := client.Admin.DeleteUser(t.Context(), username); err != nil {
|
||||
t.Logf("failed to delete integration test user %q: %v", username, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestAdminUsersCreateRequiresEmail(t *testing.T) {
|
||||
login := createIntegrationLogin(t)
|
||||
|
||||
err := runAdminCommand(t, []string{
|
||||
"admin", "users", "create",
|
||||
"--username", fmt.Sprintf("create-no-email-%d", time.Now().UnixNano()),
|
||||
"--password", "secret123",
|
||||
"--login", login.Name,
|
||||
})
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "email")
|
||||
}
|
||||
|
||||
func TestAdminUsersCreateAndDelete(t *testing.T) {
|
||||
login := createIntegrationLogin(t)
|
||||
client := login.Client()
|
||||
username := fmt.Sprintf("tea-admin-create-%d", time.Now().UnixNano())
|
||||
|
||||
err := runAdminCommand(t, []string{
|
||||
"admin", "users", "create",
|
||||
"--username", username,
|
||||
"--email", username + "@example.com",
|
||||
"--password", "secret123",
|
||||
"--admin",
|
||||
"--prohibit-login",
|
||||
"--visibility", "limited",
|
||||
"--login", login.Name,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdUser, _, err := client.Users.GetUserInfo(t.Context(), username)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, username, createdUser.UserName)
|
||||
assert.Equal(t, username+"@example.com", createdUser.Email)
|
||||
assert.True(t, createdUser.IsAdmin)
|
||||
assert.True(t, createdUser.ProhibitLogin)
|
||||
assert.Equal(t, gitea.VisibleTypeLimited, createdUser.Visibility)
|
||||
|
||||
err = runAdminCommand(t, []string{
|
||||
"admin", "users", "delete", username,
|
||||
"--confirm",
|
||||
"--login", login.Name,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = client.Users.GetUserInfo(t.Context(), username)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestAdminUsersEdit(t *testing.T) {
|
||||
login := createIntegrationLogin(t)
|
||||
client := login.Client()
|
||||
username := fmt.Sprintf("tea-admin-edit-%d", time.Now().UnixNano())
|
||||
oldPassword := "old-secret"
|
||||
newPassword := "new-secret"
|
||||
createAdminTestUser(t, client, username, oldPassword)
|
||||
|
||||
passwordFile := filepath.Join(t.TempDir(), "password.txt")
|
||||
require.NoError(t, os.WriteFile(passwordFile, []byte(newPassword+"\n"), 0o600))
|
||||
|
||||
err := runAdminCommand(t, []string{
|
||||
"admin", "users", "edit", username,
|
||||
"--email", username + "+new@example.com",
|
||||
"--full-name", "Tea Integration",
|
||||
"--restricted",
|
||||
"--password-file", passwordFile,
|
||||
"--no-must-change-password",
|
||||
"--visibility", "private",
|
||||
"--login", login.Name,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
updatedUser, _, err := client.Users.GetUserInfo(t.Context(), username)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, username+"+new@example.com", updatedUser.Email)
|
||||
assert.Equal(t, "Tea Integration", updatedUser.FullName)
|
||||
assert.True(t, updatedUser.IsActive)
|
||||
assert.True(t, updatedUser.Restricted)
|
||||
assert.False(t, updatedUser.ProhibitLogin)
|
||||
assert.Equal(t, gitea.VisibleTypePrivate, updatedUser.Visibility)
|
||||
|
||||
passwordClient, err := gitea.NewClient(
|
||||
integrationGiteaURL,
|
||||
gitea.SetBasicAuth(username, newPassword),
|
||||
gitea.SetGiteaVersion(""),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
me, _, err := passwordClient.Users.GetMyUserInfo(t.Context())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, username, me.UserName)
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
gitea "gitea.dev/sdk"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func setupOrgRepoWithLabels(t *testing.T) (org *gitea.Organization, orgRepo *gitea.Repository, orgLabel, repoLabel *gitea.Label) {
|
||||
t.Helper()
|
||||
|
||||
login := createIntegrationLogin(t)
|
||||
client := login.Client()
|
||||
orgName := fmt.Sprintf("labels-org-%d", time.Now().UnixNano()%1_000_000)
|
||||
orgRepoName := fmt.Sprintf("labels-repo-%d", time.Now().UnixNano()%1_000_000)
|
||||
ctx := context.Background()
|
||||
|
||||
// Clean up any existing test data that might interfere with the test.
|
||||
_, _ = client.Repositories.DeleteRepo(ctx, orgName, orgRepoName)
|
||||
_, _ = client.Organizations.DeleteOrg(ctx, orgName)
|
||||
|
||||
org, _, err := client.Organizations.CreateOrg(ctx, gitea.CreateOrgOption{Name: orgName})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
if _, delErr := client.Organizations.DeleteOrg(ctx, orgName); delErr != nil {
|
||||
t.Logf("failed to delete integration test org %q: %v", orgName, delErr)
|
||||
}
|
||||
})
|
||||
|
||||
orgRepo, _, err = client.Repositories.CreateOrgRepo(ctx, orgName, gitea.CreateRepoOption{Name: orgRepoName})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
if _, delErr := client.Repositories.DeleteRepo(ctx, orgName, orgRepoName); delErr != nil {
|
||||
t.Logf("failed to delete integration test repo %q: %v", orgRepoName, delErr)
|
||||
}
|
||||
})
|
||||
|
||||
orgLabelName := fmt.Sprintf("org-label-%d", time.Now().UnixNano()%1_000_000)
|
||||
repoLabelName := fmt.Sprintf("repo-label-%d", time.Now().UnixNano()%1_000_000)
|
||||
|
||||
orgLabel, _, err = client.Organizations.CreateOrgLabel(ctx, orgName, gitea.CreateOrgLabelOption{Name: orgLabelName, Color: "ff0000"})
|
||||
require.NoError(t, err)
|
||||
repoLabel, _, err = client.Repositories.CreateLabel(ctx, orgName, orgRepoName, gitea.CreateLabelOption{Name: repoLabelName, Color: "00ff00"})
|
||||
require.NoError(t, err)
|
||||
return org, orgRepo, orgLabel, repoLabel
|
||||
}
|
||||
|
||||
func setupRepoWithLabels(t *testing.T) (repo *gitea.Repository, repoLabel *gitea.Label) {
|
||||
t.Helper()
|
||||
|
||||
login := createIntegrationLogin(t)
|
||||
client := login.Client()
|
||||
repoName := fmt.Sprintf("labels-repo-%d", time.Now().UnixNano()%1_000_000)
|
||||
ctx := context.Background()
|
||||
|
||||
// Clean up any existing test data that might interfere with the test.
|
||||
_, _ = client.Repositories.DeleteRepo(ctx, login.User, repoName)
|
||||
|
||||
repo, _, err := client.Repositories.CreateRepo(ctx, gitea.CreateRepoOption{Name: repoName})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
if _, delErr := client.Repositories.DeleteRepo(ctx, login.User, repoName); delErr != nil {
|
||||
t.Logf("failed to delete integration test repo %q: %v", repoName, delErr)
|
||||
}
|
||||
})
|
||||
|
||||
repoLabelName := fmt.Sprintf("repo-label-%d", time.Now().UnixNano()%1_000_000)
|
||||
repoLabel, _, err = client.Repositories.CreateLabel(ctx, login.User, repoName, gitea.CreateLabelOption{Name: repoLabelName, Color: "00ff00"})
|
||||
require.NoError(t, err)
|
||||
return repo, repoLabel
|
||||
}
|
||||
|
||||
func TestRunLabelsList_OrgFlagReturnsOnlyOrgLabels(t *testing.T) {
|
||||
// This test verifies that when listing labels with only the --org flag set,
|
||||
// only organization labels are included in the results, and repository labels are excluded.
|
||||
_, orgRepo, orgLabel, _ := setupOrgRepoWithLabels(t)
|
||||
|
||||
labelIDs := runTeaCommand(t, "label", "ls", "--org", orgRepo.Owner.UserName, "--output", "json")
|
||||
var rows []map[string]string
|
||||
require.NoError(t, json.Unmarshal([]byte(labelIDs), &rows))
|
||||
|
||||
require.Len(t, rows, 1)
|
||||
indexInt, err := strconv.ParseInt(rows[0]["index"], 10, 64)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, orgLabel.ID, indexInt)
|
||||
require.Equal(t, orgLabel.Name, rows[0]["name"])
|
||||
}
|
||||
|
||||
func TestRunLabelsList_RepoReturnsNoOrgLabels(t *testing.T) {
|
||||
// This test verifies that when listing labels for a repository with no organization,
|
||||
// organization labels are not included in the results.
|
||||
repo, repoLabel := setupRepoWithLabels(t)
|
||||
|
||||
labelIDs := runTeaCommand(t, "label", "ls", "--repo", repo.FullName, "--output", "json")
|
||||
var rows []map[string]string
|
||||
require.NoError(t, json.Unmarshal([]byte(labelIDs), &rows))
|
||||
|
||||
require.Len(t, rows, 1)
|
||||
indexInt, err := strconv.ParseInt(rows[0]["index"], 10, 64)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, repoLabel.ID, indexInt)
|
||||
require.Equal(t, repoLabel.Name, rows[0]["name"])
|
||||
}
|
||||
|
||||
func TestRunLabelsList_UseRepoFlagOverOrgFlag(t *testing.T) {
|
||||
// This test verifies that when both --org and --repo flags are set,
|
||||
// the command prioritizes the --repo flag and lists labels for the repository, ignoring the --org flag.
|
||||
repo, repoLabel := setupRepoWithLabels(t)
|
||||
|
||||
labelIDs := runTeaCommand(t, "label", "ls", "--repo", repo.FullName, "--org", "some-other-org", "--output", "json")
|
||||
var rows []map[string]string
|
||||
require.NoError(t, json.Unmarshal([]byte(labelIDs), &rows))
|
||||
|
||||
require.Len(t, rows, 1)
|
||||
indexInt, err := strconv.ParseInt(rows[0]["index"], 10, 64)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, repoLabel.ID, indexInt)
|
||||
require.Equal(t, repoLabel.Name, rows[0]["name"])
|
||||
}
|
||||
|
||||
func TestRunLabelsList_OrgRepoReturnsRepoAndOrgLabels(t *testing.T) {
|
||||
// This test verifies that when listing labels for a repository that belongs to an organization,
|
||||
// both repository and organization labels are included in the results.
|
||||
_, orgRepo, orgLabel, repoLabel := setupOrgRepoWithLabels(t)
|
||||
|
||||
labelIDs := runTeaCommand(t, "label", "ls", "--repo", orgRepo.FullName, "--output", "json")
|
||||
var rows []map[string]string
|
||||
require.NoError(t, json.Unmarshal([]byte(labelIDs), &rows))
|
||||
|
||||
require.Len(t, rows, 2)
|
||||
require.ElementsMatch(t, []string{rows[0]["name"], rows[1]["name"]}, []string{orgLabel.Name, repoLabel.Name})
|
||||
require.ElementsMatch(
|
||||
t,
|
||||
[]string{rows[0]["index"], rows[1]["index"]},
|
||||
[]string{strconv.FormatInt(orgLabel.ID, 10), strconv.FormatInt(repoLabel.ID, 10)},
|
||||
)
|
||||
}
|
||||
|
||||
func TestRunLabelsList_OrgRepoWithExcludeReturnsOnlyRepoLabels(t *testing.T) {
|
||||
// This test verifies that when listing labels for a repository that belongs to an organization with the --exclude flag,
|
||||
// only repository labels are included in the results, and organization labels are excluded.
|
||||
_, orgRepo, _, repoLabel := setupOrgRepoWithLabels(t)
|
||||
|
||||
labelIDs := runTeaCommand(t, "label", "ls", "--repo", orgRepo.FullName, "--exclude-org", "--output", "json")
|
||||
var rows []map[string]string
|
||||
require.NoError(t, json.Unmarshal([]byte(labelIDs), &rows))
|
||||
|
||||
require.Len(t, rows, 1)
|
||||
indexInt, err := strconv.ParseInt(rows[0]["index"], 10, 64)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, repoLabel.ID, indexInt)
|
||||
require.Equal(t, repoLabel.Name, rows[0]["name"])
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.dev/sdk"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestCommentsCommandLifecycle exercises `tea comments add`, `list`, `edit`,
|
||||
// and `delete` end-to-end against a live Gitea instance. It creates a throwaway
|
||||
// repo with one issue, then drives the comment subcommands and verifies state
|
||||
// via the SDK after each step.
|
||||
func TestCommentsCommandLifecycle(t *testing.T) {
|
||||
login := createIntegrationLogin(t)
|
||||
client := login.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
repoName := fmt.Sprintf("tea-comments-integration-%d", time.Now().UnixNano())
|
||||
_, _, err := client.CreateRepo(ctx, gitea.CreateRepoOption{Name: repoName, AutoInit: true})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
if _, delErr := client.DeleteRepo(ctx, login.User, repoName); delErr != nil {
|
||||
t.Logf("failed to delete integration test repo %q: %v", repoName, delErr)
|
||||
}
|
||||
})
|
||||
|
||||
repoSlug := fmt.Sprintf("%s/%s", login.User, repoName)
|
||||
|
||||
issue, _, err := client.Issues.CreateIssue(ctx, login.User, repoName, gitea.CreateIssueOption{
|
||||
Title: "comment test issue",
|
||||
Body: "scratch issue for tea comment integration tests",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
issueIdx := strconv.FormatInt(issue.Index, 10)
|
||||
|
||||
// add via positional body
|
||||
addOutput := runTeaCommand(t,
|
||||
"comments", "add",
|
||||
"--login", login.Name,
|
||||
"--repo", repoSlug,
|
||||
issueIdx, "first comment",
|
||||
)
|
||||
assert.Contains(t, addOutput, "first comment")
|
||||
|
||||
// add via the historical shorthand (parent command, no "add" subcommand)
|
||||
runTeaCommand(t,
|
||||
"comments",
|
||||
"--login", login.Name,
|
||||
"--repo", repoSlug,
|
||||
issueIdx, "second comment",
|
||||
)
|
||||
|
||||
// list should now show both, in tabular form with IDs
|
||||
listOutput := runTeaCommand(t,
|
||||
"comments", "list",
|
||||
"--login", login.Name,
|
||||
"--repo", repoSlug,
|
||||
issueIdx,
|
||||
)
|
||||
assert.Contains(t, listOutput, "first comment")
|
||||
assert.Contains(t, listOutput, "second comment")
|
||||
|
||||
// pull the real IDs from the server so edit/delete have something to act on
|
||||
apiComments, _, err := client.Issues.ListIssueComments(ctx, login.User, repoName, issue.Index, gitea.ListIssueCommentOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, apiComments, 2)
|
||||
|
||||
var firstID, secondID int64
|
||||
for _, c := range apiComments {
|
||||
switch {
|
||||
case strings.Contains(c.Body, "first comment"):
|
||||
firstID = c.ID
|
||||
case strings.Contains(c.Body, "second comment"):
|
||||
secondID = c.ID
|
||||
}
|
||||
}
|
||||
require.NotZero(t, firstID, "first comment id not found in listing")
|
||||
require.NotZero(t, secondID, "second comment id not found in listing")
|
||||
|
||||
// edit the first comment via positional body
|
||||
editOutput := runTeaCommand(t,
|
||||
"comments", "edit",
|
||||
"--login", login.Name,
|
||||
"--repo", repoSlug,
|
||||
strconv.FormatInt(firstID, 10), "first comment (edited)",
|
||||
)
|
||||
assert.Contains(t, editOutput, "first comment (edited)")
|
||||
|
||||
edited, _, err := client.Issues.GetIssueComment(ctx, login.User, repoName, firstID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "first comment (edited)", edited.Body)
|
||||
|
||||
// delete both comments in one batch
|
||||
deleteOutput := runTeaCommand(t,
|
||||
"comments", "delete",
|
||||
"--login", login.Name,
|
||||
"--repo", repoSlug,
|
||||
strconv.FormatInt(firstID, 10),
|
||||
strconv.FormatInt(secondID, 10),
|
||||
)
|
||||
assert.Contains(t, deleteOutput, fmt.Sprintf("Deleted comment %d", firstID))
|
||||
assert.Contains(t, deleteOutput, fmt.Sprintf("Deleted comment %d", secondID))
|
||||
|
||||
remaining, _, err := client.Issues.ListIssueComments(ctx, login.User, repoName, issue.Index, gitea.ListIssueCommentOptions{})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, remaining, "expected all comments to be deleted")
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build unix
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.dev/tea/modules/config"
|
||||
)
|
||||
|
||||
func TestConfigLock_CrossProcess(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "tea-lock-test")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
lockPath := filepath.Join(tmpDir, "config.yml.lock")
|
||||
|
||||
unlock, err := config.AcquireConfigLockForTesting(lockPath, 5*time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to acquire lock: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := unlock(); err != nil {
|
||||
t.Fatalf("failed to release lock: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
script := fmt.Sprintf(`
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func main() {
|
||||
file, err := os.OpenFile(%q, os.O_CREATE|os.O_RDWR, 0o600)
|
||||
if err != nil {
|
||||
os.Exit(2)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
|
||||
if err != nil {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
syscall.Flock(int(file.Fd()), syscall.LOCK_UN)
|
||||
os.Exit(1)
|
||||
}
|
||||
`, lockPath)
|
||||
|
||||
scriptPath := filepath.Join(tmpDir, "locktest.go")
|
||||
if err := os.WriteFile(scriptPath, []byte(script), 0o600); err != nil {
|
||||
t.Fatalf("failed to write test script: %v", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command("go", "run", scriptPath)
|
||||
if err := cmd.Run(); err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
if exitErr.ExitCode() == 1 {
|
||||
t.Error("subprocess acquired lock when it should have been held")
|
||||
} else if exitErr.ExitCode() == 2 {
|
||||
t.Errorf("subprocess failed to open lock file: %v", err)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("subprocess execution failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"gitea.dev/tea/modules/config"
|
||||
teacontext "gitea.dev/tea/modules/context"
|
||||
)
|
||||
|
||||
func TestInitCommand_WithRepoSlugKeepsLocalRepoDetection(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
config.SetConfigForTesting(config.LocalConfig{
|
||||
Logins: []config.Login{{
|
||||
Name: "test-login",
|
||||
URL: "https://gitea.example.com",
|
||||
Token: "token",
|
||||
User: "login-user",
|
||||
Default: true,
|
||||
}},
|
||||
})
|
||||
|
||||
cmd := exec.Command("git", "init", "--object-format=sha256", tmpDir)
|
||||
cmd.Env = os.Environ()
|
||||
require.NoError(t, cmd.Run())
|
||||
|
||||
oldWd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, os.Chdir(tmpDir))
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, os.Chdir(oldWd))
|
||||
})
|
||||
|
||||
cliCmd := cli.Command{
|
||||
Name: "branches",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{Name: "login"},
|
||||
&cli.StringFlag{Name: "repo"},
|
||||
&cli.StringFlag{Name: "remote"},
|
||||
&cli.StringFlag{Name: "output"},
|
||||
},
|
||||
}
|
||||
require.NoError(t, cliCmd.Set("repo", "owner/repo"))
|
||||
|
||||
ctx, err := teacontext.InitCommand(&cliCmd)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "owner", ctx.Owner)
|
||||
require.Equal(t, "repo", ctx.Repo)
|
||||
require.Equal(t, "owner/repo", ctx.RepoSlug)
|
||||
require.NotNil(t, ctx.LocalRepo)
|
||||
require.NotNil(t, ctx.Login)
|
||||
require.Equal(t, "test-login", ctx.Login.Name)
|
||||
|
||||
cliCmd = cli.Command{
|
||||
Name: "pr",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{Name: "repo"},
|
||||
&cli.StringFlag{Name: "base"},
|
||||
},
|
||||
}
|
||||
require.NoError(t, cliCmd.Set("repo", "owner/repo"))
|
||||
require.NoError(t, cliCmd.Set("base", "main"))
|
||||
|
||||
ctx, err = teacontext.InitCommand(&cliCmd)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "repo", ctx.Repo)
|
||||
require.Equal(t, "owner/repo", ctx.RepoSlug)
|
||||
require.Equal(t, "main", ctx.String("base"))
|
||||
require.NotNil(t, ctx.LocalRepo)
|
||||
require.NotNil(t, ctx.Login)
|
||||
require.Equal(t, "test-login", ctx.Login.Name)
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
teagit "gitea.dev/tea/modules/git"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTeaCheckoutRemoteReferenceKeepsWorktreeClean(t *testing.T) {
|
||||
clonePath := setupGitCheckoutTestRepo(t)
|
||||
t.Chdir(clonePath)
|
||||
|
||||
repo, err := teagit.RepoFromPath(clonePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.TeaCheckout(teagit.NewRemoteReferenceName("origin", "feature/test-branch"))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Empty(t, gitOutput(t, clonePath, "status", "--porcelain"))
|
||||
assert.Equal(t, "HEAD", gitOutput(t, clonePath, "rev-parse", "--abbrev-ref", "HEAD"))
|
||||
}
|
||||
|
||||
func TestTeaCreateBranchTracksRemoteBranch(t *testing.T) {
|
||||
clonePath := setupGitCheckoutTestRepo(t)
|
||||
t.Chdir(clonePath)
|
||||
|
||||
repo, err := teagit.RepoFromPath(clonePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.TeaCreateBranch("pulls/123", "feature/test-branch", "origin")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.TeaCheckout(teagit.NewBranchReferenceName("pulls/123"))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Empty(t, gitOutput(t, clonePath, "status", "--porcelain"))
|
||||
assert.Equal(t, "origin", gitOutput(t, clonePath, "config", "--get", "branch.pulls/123.remote"))
|
||||
assert.Equal(t, "refs/heads/feature/test-branch", gitOutput(t, clonePath, "config", "--get", "branch.pulls/123.merge"))
|
||||
assert.Equal(t, "pulls/123", gitOutput(t, clonePath, "rev-parse", "--abbrev-ref", "HEAD"))
|
||||
}
|
||||
|
||||
func setupGitCheckoutTestRepo(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
remotePath := filepath.Join(tmpDir, "remote.git")
|
||||
seedPath := filepath.Join(tmpDir, "seed")
|
||||
clonePath := filepath.Join(tmpDir, "clone")
|
||||
|
||||
runGit(t, tmpDir, "init", "--bare", remotePath)
|
||||
runGit(t, tmpDir, "init", seedPath)
|
||||
runGit(t, seedPath, "config", "user.email", "test@example.com")
|
||||
runGit(t, seedPath, "config", "user.name", "Test User")
|
||||
|
||||
require.NoError(t, os.WriteFile(filepath.Join(seedPath, "README.md"), []byte("# Test Repo\n"), 0o644))
|
||||
runGit(t, seedPath, "add", "README.md")
|
||||
runGit(t, seedPath, "commit", "-m", "Initial commit")
|
||||
runGit(t, seedPath, "branch", "-M", "main")
|
||||
runGit(t, seedPath, "remote", "add", "origin", remotePath)
|
||||
runGit(t, seedPath, "push", "-u", "origin", "main")
|
||||
|
||||
runGit(t, seedPath, "checkout", "-b", "feature/test-branch")
|
||||
require.NoError(t, os.WriteFile(filepath.Join(seedPath, "feature.txt"), []byte("feature\n"), 0o644))
|
||||
runGit(t, seedPath, "add", "feature.txt")
|
||||
runGit(t, seedPath, "commit", "-m", "Add feature")
|
||||
runGit(t, seedPath, "push", "-u", "origin", "feature/test-branch")
|
||||
|
||||
runGit(t, tmpDir, "clone", remotePath, clonePath)
|
||||
return clonePath
|
||||
}
|
||||
|
||||
func runGit(t *testing.T, dir string, args ...string) {
|
||||
t.Helper()
|
||||
|
||||
cmd := exec.Command("git", args...)
|
||||
cmd.Dir = dir
|
||||
output, err := cmd.CombinedOutput()
|
||||
require.NoErrorf(t, err, "git %s failed: %s", strings.Join(args, " "), strings.TrimSpace(string(output)))
|
||||
}
|
||||
|
||||
func gitOutput(t *testing.T, dir string, args ...string) string {
|
||||
t.Helper()
|
||||
|
||||
cmd := exec.Command("git", args...)
|
||||
cmd.Dir = dir
|
||||
output, err := cmd.CombinedOutput()
|
||||
require.NoErrorf(t, err, "git %s failed: %s", strings.Join(args, " "), strings.TrimSpace(string(output)))
|
||||
return strings.TrimSpace(string(output))
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
teagit "gitea.dev/tea/modules/git"
|
||||
)
|
||||
|
||||
func TestRepoFromPath_Worktree(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "tea-worktree-test-*")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
mainRepoPath := filepath.Join(tmpDir, "main-repo")
|
||||
worktreePath := filepath.Join(tmpDir, "worktree")
|
||||
|
||||
cmd := exec.Command("git", "init", mainRepoPath)
|
||||
assert.NoError(t, cmd.Run())
|
||||
|
||||
cmd = exec.Command("git", "-C", mainRepoPath, "config", "user.email", "test@example.com")
|
||||
assert.NoError(t, cmd.Run())
|
||||
cmd = exec.Command("git", "-C", mainRepoPath, "config", "user.name", "Test User")
|
||||
assert.NoError(t, cmd.Run())
|
||||
cmd = exec.Command("git", "-C", mainRepoPath, "config", "commit.gpgsign", "false")
|
||||
assert.NoError(t, cmd.Run())
|
||||
|
||||
cmd = exec.Command("git", "-C", mainRepoPath, "remote", "add", "origin", "https://gitea.com/owner/repo.git")
|
||||
assert.NoError(t, cmd.Run())
|
||||
|
||||
readmePath := filepath.Join(mainRepoPath, "README.md")
|
||||
err = os.WriteFile(readmePath, []byte("# Test Repo\n"), 0o644)
|
||||
assert.NoError(t, err)
|
||||
cmd = exec.Command("git", "-C", mainRepoPath, "add", "README.md")
|
||||
assert.NoError(t, cmd.Run())
|
||||
cmd = exec.Command("git", "-C", mainRepoPath, "commit", "-m", "Initial commit")
|
||||
assert.NoError(t, cmd.Run())
|
||||
|
||||
cmd = exec.Command("git", "-C", mainRepoPath, "worktree", "add", worktreePath, "-b", "test-branch")
|
||||
assert.NoError(t, cmd.Run())
|
||||
|
||||
repo, err := teagit.RepoFromPath(worktreePath)
|
||||
assert.NoError(t, err, "Should be able to open worktree")
|
||||
|
||||
config, err := repo.Config()
|
||||
assert.NoError(t, err, "Should be able to read config")
|
||||
assert.NotEmpty(t, config.Remotes, "Should be able to read remotes from worktree")
|
||||
assert.Contains(t, config.Remotes, "origin", "Should have origin remote")
|
||||
assert.Equal(t, "https://gitea.com/owner/repo.git", config.Remotes["origin"].URLs[0], "Should have correct remote URL")
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
stdctx "context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
gitea "gitea.dev/sdk"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitea.dev/tea/modules/config"
|
||||
"gitea.dev/tea/modules/task"
|
||||
)
|
||||
|
||||
var (
|
||||
integrationGiteaURL string
|
||||
integrationUsername string
|
||||
integrationPassword string
|
||||
integrationToken string
|
||||
integrationTokenID int64
|
||||
integrationSetupErr error
|
||||
integrationClient *gitea.Client
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
integrationGiteaURL = os.Getenv("GITEA_TEA_TEST_URL")
|
||||
integrationUsername = os.Getenv("GITEA_TEA_TEST_USERNAME")
|
||||
integrationPassword = os.Getenv("GITEA_TEA_TEST_PASSWORD")
|
||||
|
||||
if integrationGiteaURL != "" {
|
||||
if integrationUsername == "" || integrationPassword == "" {
|
||||
integrationSetupErr = fmt.Errorf("GITEA_TEA_TEST_USERNAME and GITEA_TEA_TEST_PASSWORD are required for integration tests")
|
||||
} else {
|
||||
integrationClient, integrationSetupErr = gitea.NewClient(
|
||||
integrationGiteaURL,
|
||||
gitea.SetBasicAuth(integrationUsername, integrationPassword),
|
||||
gitea.SetGiteaVersion(""),
|
||||
)
|
||||
if integrationSetupErr == nil {
|
||||
tokenName := fmt.Sprintf("tea-integration-%d", time.Now().UnixNano())
|
||||
var token *gitea.AccessToken
|
||||
token, _, integrationSetupErr = integrationClient.Users.CreateAccessToken(stdctx.Background(), gitea.CreateAccessTokenOption{
|
||||
Name: tokenName,
|
||||
Scopes: []gitea.AccessTokenScope{gitea.AccessTokenScopeAll},
|
||||
})
|
||||
if integrationSetupErr == nil {
|
||||
integrationToken = token.Token
|
||||
integrationTokenID = token.ID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exitCode := m.Run()
|
||||
|
||||
if integrationClient != nil && integrationTokenID != 0 {
|
||||
if _, err := integrationClient.Users.DeleteAccessToken(stdctx.Background(), integrationTokenID); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to delete integration token %d: %v\n", integrationTokenID, err)
|
||||
if exitCode == 0 {
|
||||
exitCode = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
func useTempConfigPath(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
configPath := filepath.Join(t.TempDir(), "config.yml")
|
||||
config.SetConfigPathForTesting(configPath)
|
||||
config.SetConfigForTesting(config.LocalConfig{})
|
||||
t.Cleanup(func() {
|
||||
config.SetConfigForTesting(config.LocalConfig{})
|
||||
config.SetConfigPathForTesting("")
|
||||
})
|
||||
|
||||
return configPath
|
||||
}
|
||||
|
||||
func createIntegrationLogin(t *testing.T) *config.Login {
|
||||
t.Helper()
|
||||
|
||||
_ = useTempConfigPath(t)
|
||||
if integrationGiteaURL == "" {
|
||||
t.Skip("GITEA_TEA_TEST_URL is not set, skipping integration test")
|
||||
}
|
||||
require.NoError(t, integrationSetupErr)
|
||||
|
||||
require.NotEmpty(t, integrationToken, "integration token setup failed")
|
||||
|
||||
require.NoError(t, task.CreateLogin(t.Context(), "integration", integrationToken, "", "", "", "", "", integrationGiteaURL, "", "", true, false, false, false))
|
||||
|
||||
login, err := config.GetLoginByName("integration")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, login)
|
||||
|
||||
return login
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.dev/tea/cmd/pulls"
|
||||
|
||||
gitea "gitea.dev/sdk"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
func TestPullsReply(t *testing.T) {
|
||||
login := createIntegrationLogin(t)
|
||||
client := login.Client()
|
||||
timestamp := time.Now().UnixNano()
|
||||
repoName := fmt.Sprintf("tea-pr-reply-%d", timestamp)
|
||||
featureBranch := fmt.Sprintf("reply-test-%d", timestamp)
|
||||
replyBody := fmt.Sprintf("Thanks for the review %d", timestamp)
|
||||
|
||||
repo, _, err := client.Repositories.CreateRepo(t.Context(), gitea.CreateRepoOption{
|
||||
Name: repoName,
|
||||
AutoInit: true,
|
||||
DefaultBranch: "main",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
if _, delErr := client.Repositories.DeleteRepo(t.Context(), login.User, repoName); delErr != nil {
|
||||
t.Logf("failed to delete integration test repo %q: %v", repoName, delErr)
|
||||
}
|
||||
})
|
||||
|
||||
baseBranch := repo.DefaultBranch
|
||||
if baseBranch == "" {
|
||||
baseBranch = "main"
|
||||
}
|
||||
|
||||
_, _, err = client.Repositories.CreateFile(t.Context(), login.User, repoName, "review.txt", gitea.CreateFileOptions{
|
||||
FileOptions: gitea.FileOptions{
|
||||
Message: "add review target",
|
||||
BranchName: baseBranch,
|
||||
NewBranchName: featureBranch,
|
||||
},
|
||||
Content: base64.StdEncoding.EncodeToString([]byte("line for review\n")),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
pr, _, err := client.PullRequests.CreatePullRequest(t.Context(), login.User, repoName, gitea.CreatePullRequestOption{
|
||||
Base: baseBranch,
|
||||
Head: featureBranch,
|
||||
Title: "Integration test for pr reply",
|
||||
Body: "Adds a file so we can reply to a review comment.",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
review, _, err := client.PullRequests.CreatePullReview(t.Context(), login.User, repoName, pr.Index, gitea.CreatePullReviewOptions{
|
||||
State: gitea.ReviewStateComment,
|
||||
Body: "Please take another look.",
|
||||
Comments: []gitea.CreatePullReviewComment{{
|
||||
Path: "review.txt",
|
||||
Body: "Could you clarify this line?",
|
||||
NewLineNum: 1,
|
||||
}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
comments, _, err := client.PullRequests.ListPullReviewComments(t.Context(), login.User, repoName, pr.Index, review.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, comments, 1)
|
||||
|
||||
pullsCmd := &cli.Command{
|
||||
Name: "pulls",
|
||||
Commands: []*cli.Command{&pulls.CmdPullsReply},
|
||||
}
|
||||
|
||||
err = pullsCmd.Run(context.Background(), []string{
|
||||
"pulls",
|
||||
"reply",
|
||||
strconv.FormatInt(pr.Index, 10),
|
||||
strconv.FormatInt(comments[0].ID, 10),
|
||||
replyBody,
|
||||
"--login",
|
||||
login.Name,
|
||||
"--repo",
|
||||
repo.FullName,
|
||||
})
|
||||
if err != nil && strings.Contains(err.Error(), "unknown API error: 405") {
|
||||
t.Skip("pull review comment replies are not supported by this integration Gitea instance")
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
reviewComments, _, listErr := client.PullRequests.ListPullReviewComments(t.Context(), login.User, repoName, pr.Index, review.ID)
|
||||
if listErr != nil {
|
||||
t.Logf("failed to list review comments: %v", listErr)
|
||||
return false
|
||||
}
|
||||
for _, reviewComment := range reviewComments {
|
||||
if reviewComment.Body == replyBody && reviewComment.ReviewID == review.ID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, 10*time.Second, 500*time.Millisecond)
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
gitea "gitea.dev/sdk"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"gitea.dev/tea/cmd/repos"
|
||||
)
|
||||
|
||||
func TestCreateRepoObjectFormat(t *testing.T) {
|
||||
login := createIntegrationLogin(t)
|
||||
client := login.Client()
|
||||
timestamp := time.Now().Unix()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
wantOpts gitea.CreateRepoOption
|
||||
wantErr bool
|
||||
errContains string
|
||||
}{
|
||||
{
|
||||
name: "create repo with sha1 object format",
|
||||
args: []string{"--name", fmt.Sprintf("test-sha1-%d", timestamp), "--object-format", "sha1"},
|
||||
wantOpts: gitea.CreateRepoOption{
|
||||
Name: fmt.Sprintf("test-sha1-%d", timestamp),
|
||||
ObjectFormatName: "sha1",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "create repo with sha256 object format",
|
||||
args: []string{"--name", fmt.Sprintf("test-sha256-%d", timestamp), "--object-format", "sha256"},
|
||||
wantOpts: gitea.CreateRepoOption{
|
||||
Name: fmt.Sprintf("test-sha256-%d", timestamp),
|
||||
ObjectFormatName: "sha256",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "create repo with invalid object format",
|
||||
args: []string{"--name", fmt.Sprintf("test-invalid-%d", timestamp), "--object-format", "invalid"},
|
||||
wantErr: true,
|
||||
errContains: "invalid object format",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
reposCmd := &cli.Command{
|
||||
Name: "repos",
|
||||
Commands: []*cli.Command{&repos.CmdRepoCreate},
|
||||
}
|
||||
|
||||
args := append([]string{"repos", "create"}, tt.args...)
|
||||
args = append(args, "--login", login.Name)
|
||||
|
||||
err := reposCmd.Run(t.Context(), args)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
if tt.errContains != "" {
|
||||
assert.Contains(t, err.Error(), tt.errContains)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
if _, delErr := client.Repositories.DeleteRepo(t.Context(), login.User, tt.wantOpts.Name); delErr != nil {
|
||||
t.Logf("failed to delete integration test repo %q: %v", tt.wantOpts.Name, delErr)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
gitea "gitea.dev/sdk"
|
||||
|
||||
sshkeyscmd "gitea.dev/tea/cmd/sshkeys"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v3"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// generateTestPublicKey creates a fresh ed25519 keypair and returns a temp
|
||||
// file path containing the public key in authorized_keys format.
|
||||
func generateTestPublicKey(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
_, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
sshPub, err := ssh.NewPublicKey(priv.Public())
|
||||
require.NoError(t, err)
|
||||
|
||||
pubKeyStr := fmt.Sprintf("ssh-ed25519 %s tea-test-key", base64.StdEncoding.EncodeToString(sshPub.Marshal()))
|
||||
|
||||
f, err := os.CreateTemp(t.TempDir(), "test-*.pub")
|
||||
require.NoError(t, err)
|
||||
_, err = f.WriteString(pubKeyStr)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.Close())
|
||||
|
||||
return f.Name()
|
||||
}
|
||||
|
||||
func sshKeysCmd() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "ssh-keys",
|
||||
Commands: []*cli.Command{
|
||||
&sshkeyscmd.CmdSSHKeyList,
|
||||
&sshkeyscmd.CmdSSHKeyAdd,
|
||||
&sshkeyscmd.CmdSSHKeyDelete,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHKeyAddAndDelete(t *testing.T) {
|
||||
login := createIntegrationLogin(t)
|
||||
pubKeyFile := generateTestPublicKey(t)
|
||||
keyTitle := fmt.Sprintf("tea-test-%d", time.Now().Unix())
|
||||
|
||||
cmd := sshKeysCmd()
|
||||
client := login.Client()
|
||||
|
||||
err := cmd.Run(t.Context(), []string{
|
||||
"ssh-keys", "add", pubKeyFile,
|
||||
"--title", keyTitle,
|
||||
"--login", login.Name,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
keys, _, err := client.Users.ListMyPublicKeys(t.Context(), gitea.ListPublicKeysOptions{
|
||||
ListOptions: gitea.ListOptions{Page: -1},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var addedKey *gitea.PublicKey
|
||||
for _, key := range keys {
|
||||
if key.Title == keyTitle {
|
||||
addedKey = key
|
||||
break
|
||||
}
|
||||
}
|
||||
require.NotNil(t, addedKey, "added key not found in key list")
|
||||
|
||||
t.Cleanup(func() {
|
||||
client.Users.DeletePublicKey(t.Context(), addedKey.ID) //nolint:errcheck
|
||||
})
|
||||
|
||||
err = cmd.Run(t.Context(), []string{
|
||||
"ssh-keys", "delete", strconv.FormatInt(addedKey.ID, 10),
|
||||
"--confirm",
|
||||
"--login", login.Name,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, resp, err := client.Users.GetPublicKey(t.Context(), addedKey.ID)
|
||||
assert.Error(t, err)
|
||||
if assert.NotNil(t, resp) {
|
||||
assert.Equal(t, 404, resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHKeyList(t *testing.T) {
|
||||
login := createIntegrationLogin(t)
|
||||
|
||||
cmd := sshKeysCmd()
|
||||
err := cmd.Run(t.Context(), []string{
|
||||
"ssh-keys", "list",
|
||||
"--login", login.Name,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
gitea "gitea.dev/sdk"
|
||||
"gitea.dev/tea/cmd"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEditIssue_ModifiesAssignees(t *testing.T) {
|
||||
// This test verifies that EditIssue correctly modifies assignees of an issue via set, add, and remove.
|
||||
// It sets up a test repository and organization with known users, then calls EditIssue and checks the results.
|
||||
login := createIntegrationLogin(t)
|
||||
client := login.Client()
|
||||
orgName := fmt.Sprintf("issue-org-%d", time.Now().UnixNano()%1_000_000)
|
||||
orgRepoName := fmt.Sprintf("issue-repo-%d", time.Now().UnixNano()%1_000_000)
|
||||
ctx := context.Background()
|
||||
|
||||
// Clean up any existing test data that might interfere with the test.
|
||||
_, _ = client.Repositories.DeleteRepo(ctx, orgName, orgRepoName)
|
||||
_, _ = client.Organizations.DeleteOrg(ctx, orgName)
|
||||
_, _ = client.Admin.DeleteUser(ctx, "user1")
|
||||
_, _ = client.Admin.DeleteUser(ctx, "user2")
|
||||
|
||||
_, _, err := client.Admin.CreateOrg(ctx, integrationUsername, gitea.CreateOrgOption{Name: orgName})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
if _, delErr := client.Organizations.DeleteOrg(ctx, orgName); delErr != nil {
|
||||
t.Logf("failed to delete integration test org %q: %v", orgName, delErr)
|
||||
}
|
||||
})
|
||||
|
||||
orgRepo, _, err := client.Repositories.CreateOrgRepo(ctx, orgName, gitea.CreateRepoOption{Name: orgRepoName})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
if _, delErr := client.Repositories.DeleteRepo(ctx, orgName, orgRepoName); delErr != nil {
|
||||
t.Logf("failed to delete integration test repo %q: %v", orgRepoName, delErr)
|
||||
}
|
||||
})
|
||||
|
||||
user1, _, err := client.Admin.CreateUser(ctx, gitea.CreateUserOption{Username: "user1", Password: "user1!1234", Email: "user1@test.com"})
|
||||
require.NoError(t, err)
|
||||
user2, _, err := client.Admin.CreateUser(ctx, gitea.CreateUserOption{Username: "user2", Password: "user2!1234", Email: "user2@test.com"})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_, _ = client.Admin.DeleteUser(ctx, "user1")
|
||||
_, _ = client.Admin.DeleteUser(ctx, "user2")
|
||||
})
|
||||
|
||||
permission := gitea.AccessModeOwner
|
||||
team, _, err := client.Organizations.CreateTeam(ctx, orgName, gitea.CreateTeamOption{Name: "writers", Permission: permission})
|
||||
require.NoError(t, err)
|
||||
_, err = client.Organizations.AddTeamMember(ctx, team.ID, "user1")
|
||||
require.NoError(t, err)
|
||||
_, err = client.Organizations.AddTeamMember(ctx, team.ID, "user2")
|
||||
require.NoError(t, err)
|
||||
_, err = client.Organizations.AddTeamRepository(ctx, team.ID, orgName, orgRepoName)
|
||||
require.NoError(t, err)
|
||||
assigneeValid, _, err := client.Repositories.CheckRepoIssueAssignee(ctx, orgName, orgRepoName, user1.UserName)
|
||||
require.NoError(t, err)
|
||||
require.True(t, assigneeValid)
|
||||
assigneeValid, _, err = client.Repositories.CheckRepoIssueAssignee(ctx, orgName, orgRepoName, user2.UserName)
|
||||
require.NoError(t, err)
|
||||
require.True(t, assigneeValid)
|
||||
|
||||
orgIssue, _, err := client.Issues.CreateIssue(ctx, orgName, orgRepoName, gitea.CreateIssueOption{Title: "issue_integration_test", Assignees: []string{integrationUsername}, Closed: false})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, integrationUsername, orgIssue.Assignees[0].UserName)
|
||||
curUser, _, err := client.Users.GetMyUserInfo(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
getNames := func(issue *gitea.Issue) []string {
|
||||
names := make([]string, len(issue.Assignees))
|
||||
for i, u := range issue.Assignees {
|
||||
names[i] = u.UserName
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
checkAssignees := func(expected []string) {
|
||||
updatedIssue, _, err := client.Issues.GetIssue(ctx, orgName, orgRepoName, orgIssue.Index)
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, getNames(updatedIssue), expected)
|
||||
}
|
||||
|
||||
app := cmd.App()
|
||||
// test set overwrites add and remove
|
||||
err = app.Run(ctx, []string{"tea", "i", "edit", "1", "--set-assignees", "user1", "--add-assignees", "user2", "--remove-assignees", integrationUsername, "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{user1.UserName})
|
||||
err = app.Run(ctx, []string{"tea", "i", "edit", "1", "--set-assignees", "user2," + integrationUsername, "--add-assignees", "user1", "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{user2.UserName, curUser.UserName})
|
||||
err = app.Run(ctx, []string{"tea", "i", "edit", "1", "--set-assignees", "user1", "--remove-assignees", integrationUsername, "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{user1.UserName})
|
||||
|
||||
// test remove one assignee
|
||||
err = app.Run(ctx, []string{"tea", "i", "edit", "1", "--remove-assignees", "user1", "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{})
|
||||
|
||||
// test add multiple assignees and overwrites remove
|
||||
err = app.Run(ctx, []string{"tea", "i", "edit", "1", "--add-assignees", "user1," + integrationUsername, "--remove-assignees", integrationUsername, "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{curUser.UserName, user1.UserName})
|
||||
|
||||
// test add one assignee
|
||||
err = app.Run(ctx, []string{"tea", "i", "edit", "1", "--add-assignees", "user2", "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{curUser.UserName, user1.UserName, user2.UserName})
|
||||
|
||||
// test remove multiple assignees
|
||||
err = app.Run(ctx, []string{"tea", "i", "edit", "1", "--remove-assignees", "user1,user2," + integrationUsername, "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{})
|
||||
|
||||
err = app.Run(ctx, []string{"tea", "i", "edit", "1", "--set-assignees", "user_not_exists"})
|
||||
require.Error(t, err)
|
||||
err = app.Run(ctx, []string{"tea", "i", "edit", "1", "--add-assignees", "user_not_exists"})
|
||||
require.Error(t, err)
|
||||
err = app.Run(ctx, []string{"tea", "i", "edit", "1", "--remove-assignees", "user_not_exists"})
|
||||
require.Error(t, err)
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
gitea "gitea.dev/sdk"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestResolveLabelNames_ReturnsRepoAndOrgLabels(t *testing.T) {
|
||||
// This test verifies that ResolveLabelNames correctly returns both repository and organization labels.
|
||||
// It sets up a test repository and organization with known labels, then calls ResolveLabelNames and checks the results.
|
||||
login := createIntegrationLogin(t)
|
||||
client := login.Client()
|
||||
orgName := fmt.Sprintf("labels-org-%d", time.Now().UnixNano()%1_000_000)
|
||||
orgRepoName := fmt.Sprintf("labels-repo-%d", time.Now().UnixNano()%1_000_000)
|
||||
ctx := context.Background()
|
||||
|
||||
// Clean up any existing test data that might interfere with the test.
|
||||
_, _ = client.Repositories.DeleteRepo(ctx, orgName, orgRepoName)
|
||||
_, _ = client.Organizations.DeleteOrg(ctx, orgName)
|
||||
|
||||
_, _, err := client.Admin.CreateOrg(ctx, integrationUsername, gitea.CreateOrgOption{Name: orgName})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
if _, delErr := client.Organizations.DeleteOrg(ctx, orgName); delErr != nil {
|
||||
t.Logf("failed to delete integration test org %q: %v", orgName, delErr)
|
||||
}
|
||||
})
|
||||
|
||||
orgRepo, _, err := client.Repositories.CreateOrgRepo(ctx, orgName, gitea.CreateRepoOption{Name: orgRepoName})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
if _, delErr := client.Repositories.DeleteRepo(ctx, orgName, orgRepoName); delErr != nil {
|
||||
t.Logf("failed to delete integration test repo %q: %v", orgRepoName, delErr)
|
||||
}
|
||||
})
|
||||
|
||||
orgLabelName := fmt.Sprintf("org-label-%d", time.Now().UnixNano()%1_000_000)
|
||||
repoLabelName := fmt.Sprintf("repo-label-%d", time.Now().UnixNano()%1_000_000)
|
||||
|
||||
orgLabel, _, err := client.Organizations.CreateOrgLabel(ctx, orgName, gitea.CreateOrgLabelOption{Name: orgLabelName, Color: "ff0000"})
|
||||
require.NoError(t, err)
|
||||
repoLabel, _, err := client.Repositories.CreateLabel(ctx, orgName, orgRepoName, gitea.CreateLabelOption{Name: repoLabelName, Color: "00ff00"})
|
||||
require.NoError(t, err)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
runGit := func(args ...string) {
|
||||
cmd := exec.Command("git", args...)
|
||||
cmd.Dir = tmpDir
|
||||
require.NoError(t, cmd.Run())
|
||||
}
|
||||
|
||||
runGit("init")
|
||||
runGit("config", "user.email", "test@test.com")
|
||||
runGit("config", "user.name", "test")
|
||||
httpsURL := fmt.Sprintf("%s/%s.git", login.URL, orgRepo.FullName)
|
||||
httpsURL = strings.Replace(httpsURL, "://", fmt.Sprintf("://%s:%s@", login.Name, login.Token), 1)
|
||||
|
||||
runGit("remote", "add", "origin", httpsURL)
|
||||
|
||||
runGit("checkout", "-b", "main")
|
||||
runGit("commit", "--allow-empty", "-m", "Initial commit")
|
||||
runGit("push", "-u", "origin", "HEAD:main")
|
||||
|
||||
runGit("checkout", "-b", "branch-with-labels")
|
||||
runGit("commit", "--allow-empty", "-m", "Initial commit")
|
||||
runGit("push", "-u", "origin", "HEAD:branch-with-labels")
|
||||
|
||||
waitForBranches(t, orgRepo.FullName, "branch-with-labels")
|
||||
_ = runTeaCommand(
|
||||
t, "pr", "create", "--repo", orgRepo.FullName,
|
||||
"--login", login.Name, "--base", "main", "--head", "branch-with-labels",
|
||||
"--labels", orgLabelName+","+repoLabelName,
|
||||
)
|
||||
|
||||
pr, _, err := client.PullRequests.GetPullRequest(ctx, orgName, orgRepoName, 1)
|
||||
require.NoError(t, err)
|
||||
labels := pr.Labels
|
||||
require.Len(t, labels, 2)
|
||||
require.ElementsMatch(t, labels, []*gitea.Label{orgLabel, repoLabel})
|
||||
}
|
||||
|
||||
func waitForBranches(t *testing.T, repoFullName string, branchName string) {
|
||||
t.Helper()
|
||||
url := fmt.Sprintf("%s/api/v1/repos/%s/branches", os.Getenv("GITEA_TEA_TEST_URL"), repoFullName)
|
||||
|
||||
// retry for ~6 seconds
|
||||
for range 30 {
|
||||
resp, err := http.Get(url)
|
||||
if err == nil {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
var branches []struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if json.Unmarshal(body, &branches) == nil {
|
||||
have := map[string]bool{}
|
||||
for _, b := range branches {
|
||||
have[b.Name] = true
|
||||
}
|
||||
if have["main"] && have[branchName] {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
t.Fatalf("waitForBranches: branches never appeared in API: %s", url)
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
gitea "gitea.dev/sdk"
|
||||
"gitea.dev/tea/cmd"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEditPull_ModifiesAssignees(t *testing.T) {
|
||||
// This test verifies that EditPull correctly modifies assignees of an pull request via set, add, and remove.
|
||||
// It sets up a test repository and organization with known users, then calls EditPull and checks the results.
|
||||
login := createIntegrationLogin(t)
|
||||
client := login.Client()
|
||||
orgName := fmt.Sprintf("pull-org-%d", time.Now().UnixNano()%1_000_000)
|
||||
orgRepoName := fmt.Sprintf("pull-repo-%d", time.Now().UnixNano()%1_000_000)
|
||||
ctx := context.Background()
|
||||
|
||||
// Clean up any existing test data that might interfere with the test.
|
||||
_, _ = client.Repositories.DeleteRepo(ctx, orgName, orgRepoName)
|
||||
_, _ = client.Organizations.DeleteOrg(ctx, orgName)
|
||||
_, _ = client.Admin.DeleteUser(ctx, "user1")
|
||||
_, _ = client.Admin.DeleteUser(ctx, "user2")
|
||||
|
||||
_, _, err := client.Admin.CreateOrg(ctx, integrationUsername, gitea.CreateOrgOption{Name: orgName})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
if _, delErr := client.Organizations.DeleteOrg(ctx, orgName); delErr != nil {
|
||||
t.Logf("failed to delete integration test org %q: %v", orgName, delErr)
|
||||
}
|
||||
})
|
||||
|
||||
orgRepo, _, err := client.Repositories.CreateOrgRepo(ctx, orgName, gitea.CreateRepoOption{Name: orgRepoName})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
if _, delErr := client.Repositories.DeleteRepo(ctx, orgName, orgRepoName); delErr != nil {
|
||||
t.Logf("failed to delete integration test repo %q: %v", orgRepoName, delErr)
|
||||
}
|
||||
})
|
||||
|
||||
user1, _, err := client.Admin.CreateUser(ctx, gitea.CreateUserOption{Username: "user1", Password: "user1!1234", Email: "user1@test.com"})
|
||||
require.NoError(t, err)
|
||||
user2, _, err := client.Admin.CreateUser(ctx, gitea.CreateUserOption{Username: "user2", Password: "user2!1234", Email: "user2@test.com"})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_, _ = client.Admin.DeleteUser(ctx, "user1")
|
||||
_, _ = client.Admin.DeleteUser(ctx, "user2")
|
||||
})
|
||||
|
||||
permission := gitea.AccessModeOwner
|
||||
team, _, err := client.Organizations.CreateTeam(ctx, orgName, gitea.CreateTeamOption{Name: "writers", Permission: permission})
|
||||
require.NoError(t, err)
|
||||
_, err = client.Organizations.AddTeamMember(ctx, team.ID, "user1")
|
||||
require.NoError(t, err)
|
||||
_, err = client.Organizations.AddTeamMember(ctx, team.ID, "user2")
|
||||
require.NoError(t, err)
|
||||
_, err = client.Organizations.AddTeamRepository(ctx, team.ID, orgName, orgRepoName)
|
||||
require.NoError(t, err)
|
||||
assigneeValid, _, err := client.Repositories.CheckRepoIssueAssignee(ctx, orgName, orgRepoName, user1.UserName)
|
||||
require.NoError(t, err)
|
||||
require.True(t, assigneeValid)
|
||||
assigneeValid, _, err = client.Repositories.CheckRepoIssueAssignee(ctx, orgName, orgRepoName, user2.UserName)
|
||||
require.NoError(t, err)
|
||||
require.True(t, assigneeValid)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
runGit := func(args ...string) {
|
||||
cmd := exec.Command("git", args...)
|
||||
cmd.Dir = tmpDir
|
||||
require.NoError(t, cmd.Run())
|
||||
}
|
||||
|
||||
runGit("init")
|
||||
runGit("config", "user.email", "test@test.com")
|
||||
runGit("config", "user.name", "test")
|
||||
httpsURL := fmt.Sprintf("%s/%s.git", login.URL, orgRepo.FullName)
|
||||
httpsURL = strings.Replace(httpsURL, "://", fmt.Sprintf("://%s:%s@", login.Name, login.Token), 1)
|
||||
|
||||
runGit("remote", "add", "origin", httpsURL)
|
||||
|
||||
runGit("checkout", "-b", "main")
|
||||
runGit("commit", "--allow-empty", "-m", "Initial commit")
|
||||
runGit("push", "-u", "origin", "HEAD:main")
|
||||
|
||||
runGit("checkout", "-b", "branch-with-assignees")
|
||||
runGit("commit", "--allow-empty", "-m", "Initial commit")
|
||||
runGit("push", "-u", "origin", "HEAD:branch-with-assignees")
|
||||
|
||||
waitForBranches(t, orgRepo.FullName, "branch-with-assignees")
|
||||
getNames := func(pr *gitea.PullRequest) []string {
|
||||
names := make([]string, len(pr.Assignees))
|
||||
for i, u := range pr.Assignees {
|
||||
names[i] = u.UserName
|
||||
}
|
||||
return names
|
||||
}
|
||||
checkAssignees := func(expected []string) {
|
||||
updatedPr, _, err := client.PullRequests.GetPullRequest(ctx, orgName, orgRepoName, 1)
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, getNames(updatedPr), expected)
|
||||
}
|
||||
|
||||
app := cmd.App()
|
||||
err = app.Run(ctx, []string{"tea", "pr", "create", "--repo", orgRepo.FullName, "--base", "main", "--head", "branch-with-assignees", "--a", integrationUsername})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{integrationUsername})
|
||||
curUser, _, err := client.Users.GetMyUserInfo(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// test set overwrites add and remove
|
||||
err = app.Run(ctx, []string{"tea", "pr", "edit", "1", "--set-assignees", "user1", "--add-assignees", "user2", "--remove-assignees", integrationUsername, "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{user1.UserName})
|
||||
err = app.Run(ctx, []string{"tea", "pr", "edit", "1", "--set-assignees", "user2," + integrationUsername, "--add-assignees", "user1", "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{user2.UserName, curUser.UserName})
|
||||
err = app.Run(ctx, []string{"tea", "pr", "edit", "1", "--set-assignees", "user1", "--remove-assignees", integrationUsername, "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{user1.UserName})
|
||||
|
||||
// test remove one assignee
|
||||
err = app.Run(ctx, []string{"tea", "pr", "edit", "1", "--remove-assignees", "user1", "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{})
|
||||
|
||||
// test add multiple assignees and overwrites remove
|
||||
err = app.Run(ctx, []string{"tea", "pr", "edit", "1", "--add-assignees", "user1," + integrationUsername, "--remove-assignees", integrationUsername, "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{curUser.UserName, user1.UserName})
|
||||
|
||||
// test add one assignee
|
||||
err = app.Run(ctx, []string{"tea", "pr", "edit", "1", "--add-assignees", "user2", "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{curUser.UserName, user1.UserName, user2.UserName})
|
||||
|
||||
// test remove multiple assignees
|
||||
err = app.Run(ctx, []string{"tea", "pr", "edit", "1", "--remove-assignees", "user1,user2," + integrationUsername, "--repo", orgRepo.FullName})
|
||||
require.NoError(t, err)
|
||||
checkAssignees([]string{})
|
||||
|
||||
err = app.Run(ctx, []string{"tea", "pr", "edit", "1", "--set-assignees", "user_not_exists"})
|
||||
require.Error(t, err)
|
||||
err = app.Run(ctx, []string{"tea", "pr", "edit", "1", "--add-assignees", "user_not_exists"})
|
||||
require.Error(t, err)
|
||||
err = app.Run(ctx, []string{"tea", "pr", "edit", "1", "--remove-assignees", "user_not_exists"})
|
||||
require.Error(t, err)
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
gitea "gitea.dev/sdk"
|
||||
teacmd "gitea.dev/tea/cmd"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWikiCommandLifecycle(t *testing.T) {
|
||||
login := createIntegrationLogin(t)
|
||||
client := login.Client()
|
||||
repoName := fmt.Sprintf("tea-wiki-integration-%d", time.Now().UnixNano())
|
||||
|
||||
_, _, err := client.Repositories.CreateRepo(context.Background(), gitea.CreateRepoOption{Name: repoName})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
if _, delErr := client.Repositories.DeleteRepo(context.Background(), login.User, repoName); delErr != nil {
|
||||
t.Logf("failed to delete integration test repo %q: %v", repoName, delErr)
|
||||
}
|
||||
})
|
||||
|
||||
repoSlug := fmt.Sprintf("%s/%s", login.User, repoName)
|
||||
|
||||
createOutput := runTeaCommand(t,
|
||||
"wiki", "create",
|
||||
"--login", login.Name,
|
||||
"--repo", repoSlug,
|
||||
"--title", "Home",
|
||||
"--content", "# Home",
|
||||
"--message", "create home",
|
||||
"--output", "json",
|
||||
)
|
||||
assert.Contains(t, createOutput, "\"title\": \"Home\"")
|
||||
|
||||
page, _, err := client.Wiki.GetPage(context.Background(), login.User, repoName, "Home")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Home", page.Title)
|
||||
assert.Contains(t, decodeBase64String(t, page.ContentBase64), "# Home")
|
||||
|
||||
listOutput := runTeaCommand(t,
|
||||
"wiki", "list",
|
||||
"--login", login.Name,
|
||||
"--repo", repoSlug,
|
||||
"--output", "json",
|
||||
)
|
||||
assert.Contains(t, listOutput, "\"title\": \"Home\"")
|
||||
|
||||
emptyRepoName := fmt.Sprintf("tea-wiki-empty-%d", time.Now().UnixNano())
|
||||
_, _, err = client.Repositories.CreateRepo(context.Background(), gitea.CreateRepoOption{Name: emptyRepoName})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
if _, delErr := client.Repositories.DeleteRepo(context.Background(), login.User, emptyRepoName); delErr != nil {
|
||||
t.Logf("failed to delete empty wiki repo %q: %v", emptyRepoName, delErr)
|
||||
}
|
||||
})
|
||||
emptyRepoSlug := fmt.Sprintf("%s/%s", login.User, emptyRepoName)
|
||||
emptyListOutput := runTeaCommand(t,
|
||||
"wiki", "list",
|
||||
"--login", login.Name,
|
||||
"--repo", emptyRepoSlug,
|
||||
)
|
||||
assert.Contains(t, emptyListOutput, "No wiki pages found")
|
||||
|
||||
viewOutput := runTeaCommand(t,
|
||||
"wiki", "view", "Home",
|
||||
"--login", login.Name,
|
||||
"--repo", repoSlug,
|
||||
"--output", "json",
|
||||
)
|
||||
assert.Contains(t, viewOutput, "\"title\": \"Home\"")
|
||||
assert.Contains(t, viewOutput, "\"content\": \"# Home\"")
|
||||
|
||||
runTeaCommand(t,
|
||||
"wiki", "edit", "Home",
|
||||
"--login", login.Name,
|
||||
"--repo", repoSlug,
|
||||
"--content", "# Updated",
|
||||
"--message", "edit home",
|
||||
"--output", "json",
|
||||
)
|
||||
|
||||
page, _, err = client.Wiki.GetPage(context.Background(), login.User, repoName, "Home")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Home", page.Title)
|
||||
assert.Contains(t, decodeBase64String(t, page.ContentBase64), "# Updated")
|
||||
|
||||
viewRenderedOutput := runTeaCommand(t,
|
||||
"wiki", "view", "Home",
|
||||
"--login", login.Name,
|
||||
"--repo", repoSlug,
|
||||
)
|
||||
assert.Contains(t, viewRenderedOutput, "# Updated")
|
||||
assert.NotContains(t, viewRenderedOutput, "Last updated")
|
||||
|
||||
revisionsOutput := runTeaCommand(t,
|
||||
"wiki", "revisions", "Home",
|
||||
"--login", login.Name,
|
||||
"--repo", repoSlug,
|
||||
"--output", "json",
|
||||
)
|
||||
assert.Contains(t, revisionsOutput, "create home")
|
||||
assert.Contains(t, revisionsOutput, "edit home")
|
||||
|
||||
runTeaCommand(t,
|
||||
"wiki", "delete", "Home",
|
||||
"--login", login.Name,
|
||||
"--repo", repoSlug,
|
||||
"--confirm",
|
||||
)
|
||||
|
||||
_, _, err = client.Wiki.GetPage(context.Background(), login.User, repoName, "Home")
|
||||
require.Error(t, err)
|
||||
assert.True(t, strings.Contains(strings.ToLower(err.Error()), "not found") || strings.Contains(strings.ToLower(err.Error()), "404"))
|
||||
}
|
||||
|
||||
func runTeaCommand(t *testing.T, args ...string) string {
|
||||
t.Helper()
|
||||
|
||||
app := teacmd.App()
|
||||
errBuf := &bytes.Buffer{}
|
||||
app.Writer = io.Discard
|
||||
app.ErrWriter = errBuf
|
||||
|
||||
oldStdout := os.Stdout
|
||||
reader, writer, err := os.Pipe()
|
||||
require.NoError(t, err)
|
||||
os.Stdout = writer
|
||||
defer func() {
|
||||
os.Stdout = oldStdout
|
||||
}()
|
||||
|
||||
done := make(chan string, 1)
|
||||
go func() {
|
||||
var outBuf bytes.Buffer
|
||||
_, _ = io.Copy(&outBuf, reader)
|
||||
done <- outBuf.String()
|
||||
}()
|
||||
|
||||
runErr := app.Run(context.Background(), append([]string{"tea"}, args...))
|
||||
require.NoError(t, writer.Close())
|
||||
output := <-done
|
||||
require.NoError(t, reader.Close())
|
||||
|
||||
require.NoError(t, runErr, "tea %s failed with stdout: %s\nstderr: %s", strings.Join(args, " "), output, errBuf.String())
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
func decodeBase64String(t *testing.T, encoded string) string {
|
||||
t.Helper()
|
||||
|
||||
decoded, err := base64.StdEncoding.DecodeString(encoded)
|
||||
require.NoError(t, err)
|
||||
|
||||
return string(decoded)
|
||||
}
|
||||
Reference in new issue
Block a user