feat: vendor gitea 1.16.2
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestApproveAllRunsOnPullRequestPage(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
// user2 is the owner of the base repo
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
user2Session := loginUser(t, user2.Name)
|
||||
user2Token := getTokenForLoggedInUser(t, user2Session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
// user4 is the owner of the fork repo
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
user4Session := loginUser(t, user4.Name)
|
||||
user4Token := getTokenForLoggedInUser(t, loginUser(t, user4.Name), auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
|
||||
apiBaseRepo := createActionsTestRepo(t, user2Token, "approve-all-runs", false)
|
||||
baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiBaseRepo.ID})
|
||||
user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository)
|
||||
defer doAPIDeleteRepository(user2APICtx)(t)
|
||||
|
||||
runner := newMockRunner()
|
||||
runner.registerAsRepoRunner(t, baseRepo.OwnerName, baseRepo.Name, "mock-runner", []string{"ubuntu-latest"}, false)
|
||||
|
||||
// init workflows
|
||||
wf1TreePath := ".gitea/workflows/pull_1.yml"
|
||||
wf1FileContent := `name: Pull 1
|
||||
on: pull_request
|
||||
jobs:
|
||||
unit-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo unit-test
|
||||
`
|
||||
opts1 := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, "create %s"+wf1TreePath, wf1FileContent)
|
||||
createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wf1TreePath, opts1)
|
||||
wf2TreePath := ".gitea/workflows/pull_2.yml"
|
||||
wf2FileContent := `name: Pull 2
|
||||
on: pull_request
|
||||
jobs:
|
||||
integration-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo integration-test
|
||||
`
|
||||
opts2 := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, "create %s"+wf2TreePath, wf2FileContent)
|
||||
createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wf2TreePath, opts2)
|
||||
|
||||
// user4 forks the repo
|
||||
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/forks", baseRepo.OwnerName, baseRepo.Name),
|
||||
&api.CreateForkOption{
|
||||
Name: new("approve-all-runs-fork"),
|
||||
}).AddTokenAuth(user4Token)
|
||||
resp := MakeRequest(t, req, http.StatusAccepted)
|
||||
var apiForkRepo api.Repository
|
||||
DecodeJSON(t, resp, &apiForkRepo)
|
||||
forkRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiForkRepo.ID})
|
||||
user4APICtx := NewAPITestContext(t, user4.Name, forkRepo.Name, auth_model.AccessTokenScopeWriteRepository)
|
||||
defer doAPIDeleteRepository(user4APICtx)(t)
|
||||
|
||||
// user4 creates a pull request from branch "bugfix/user4"
|
||||
doAPICreateFile(user4APICtx, "user4-fix.txt", &api.CreateFileOptions{
|
||||
FileOptions: api.FileOptions{
|
||||
NewBranchName: "bugfix/user4",
|
||||
Message: "create user4-fix.txt",
|
||||
Author: api.Identity{
|
||||
Name: user4.Name,
|
||||
Email: user4.Email,
|
||||
},
|
||||
Committer: api.Identity{
|
||||
Name: user4.Name,
|
||||
Email: user4.Email,
|
||||
},
|
||||
Dates: api.CommitDateOptions{
|
||||
Author: time.Now(),
|
||||
Committer: time.Now(),
|
||||
},
|
||||
},
|
||||
ContentBase64: base64.StdEncoding.EncodeToString([]byte("user4-fix")),
|
||||
})(t)
|
||||
apiPull, err := doAPICreatePullRequest(user4APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, user4.Name+":bugfix/user4")(t)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// check runs
|
||||
run1 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: baseRepo.ID, TriggerUserID: user4.ID, WorkflowID: "pull_1.yml"})
|
||||
assert.True(t, run1.NeedApproval)
|
||||
assert.Equal(t, actions_model.StatusBlocked, run1.Status)
|
||||
run2 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: baseRepo.ID, TriggerUserID: user4.ID, WorkflowID: "pull_2.yml"})
|
||||
assert.True(t, run2.NeedApproval)
|
||||
assert.Equal(t, actions_model.StatusBlocked, run2.Status)
|
||||
|
||||
// user4 cannot see the approve button
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d", baseRepo.OwnerName, baseRepo.Name, apiPull.Index))
|
||||
resp = user4Session.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
assert.Zero(t, htmlDoc.doc.Find("#approve-status-checks button.link-action").Length())
|
||||
|
||||
// user2 can see the approve button
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d", baseRepo.OwnerName, baseRepo.Name, apiPull.Index))
|
||||
resp = user2Session.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc = NewHTMLParser(t, resp.Body)
|
||||
dataURL, exist := htmlDoc.doc.Find("#approve-status-checks button.link-action").Attr("data-url")
|
||||
assert.True(t, exist)
|
||||
assert.Equal(t,
|
||||
fmt.Sprintf("%s/actions/approve-all-checks?commit_id=%s",
|
||||
baseRepo.Link(), apiPull.Head.Sha),
|
||||
dataURL,
|
||||
)
|
||||
|
||||
// user2 approves all runs
|
||||
req = NewRequest(t, "POST", dataURL)
|
||||
user2Session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// check runs
|
||||
run1 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run1.ID})
|
||||
assert.False(t, run1.NeedApproval)
|
||||
assert.Equal(t, user2.ID, run1.ApprovedBy)
|
||||
assert.Equal(t, actions_model.StatusWaiting, run1.Status)
|
||||
run2 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run2.ID})
|
||||
assert.False(t, run2.NeedApproval)
|
||||
assert.Equal(t, user2.ID, run2.ApprovedBy)
|
||||
assert.Equal(t, actions_model.StatusWaiting, run2.Status)
|
||||
})
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -121,59 +122,55 @@ jobs:
|
||||
opts := getWorkflowCreateFileOptions(user2, apiRepo.DefaultBranch, "create "+testCase.treePath, testCase.fileContent)
|
||||
createWorkflowFile(t, token, user2.Name, apiRepo.Name, testCase.treePath, opts)
|
||||
|
||||
runIndex := ""
|
||||
var runID int64
|
||||
for i := 0; i < len(testCase.outcomes); i++ {
|
||||
task := runner.fetchTask(t)
|
||||
jobName := getTaskJobNameByTaskID(t, token, user2.Name, apiRepo.Name, task.Id)
|
||||
outcome := testCase.outcomes[jobName]
|
||||
assert.NotNil(t, outcome)
|
||||
runner.execTask(t, task, outcome)
|
||||
runIndex = task.Context.GetFields()["run_number"].GetStringValue()
|
||||
assert.Equal(t, "1", runIndex)
|
||||
runIndex := task.Context.GetFields()["run_number"].GetStringValue()
|
||||
parsedRunIndex, err := strconv.ParseInt(runIndex, 10, 64)
|
||||
assert.NoError(t, err)
|
||||
run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: apiRepo.ID, Index: parsedRunIndex})
|
||||
runID = run.ID
|
||||
}
|
||||
|
||||
jobs, err := actions_model.GetRunJobsByRunID(t.Context(), runID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for i := 0; i < len(testCase.outcomes); i++ {
|
||||
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%s/jobs/%d", user2.Name, apiRepo.Name, runIndex, i), map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
})
|
||||
jobID := jobs[i].ID
|
||||
req := NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d", user2.Name, apiRepo.Name, runID, jobID))
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
var listResp actions.ViewResponse
|
||||
err := json.Unmarshal(resp.Body.Bytes(), &listResp)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, listResp.State.Run.Jobs, 3)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%s/jobs/%d/logs", user2.Name, apiRepo.Name, runIndex, i)).
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/logs", user2.Name, apiRepo.Name, runID, jobID)).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
}
|
||||
|
||||
req := NewRequestWithValues(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%s", user2.Name, apiRepo.Name, runIndex), map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
})
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d", user2.Name, apiRepo.Name, runID))
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%s/delete", user2.Name, apiRepo.Name, runIndex), map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
})
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/delete", user2.Name, apiRepo.Name, runID))
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%s/delete", user2.Name, apiRepo.Name, runIndex), map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
})
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/delete", user2.Name, apiRepo.Name, runID))
|
||||
session.MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
req = NewRequestWithValues(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%s", user2.Name, apiRepo.Name, runIndex), map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
})
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d", user2.Name, apiRepo.Name, runID))
|
||||
session.MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
for i := 0; i < len(testCase.outcomes); i++ {
|
||||
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%s/jobs/%d", user2.Name, apiRepo.Name, runIndex, i), map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
})
|
||||
jobID := jobs[i].ID
|
||||
req := NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d", user2.Name, apiRepo.Name, runID, jobID))
|
||||
session.MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%s/jobs/%d/logs", user2.Name, apiRepo.Name, runIndex, i)).
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/logs", user2.Name, apiRepo.Name, runID, jobID)).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
}
|
||||
|
||||
@@ -62,9 +62,8 @@ jobs:
|
||||
// run the workflow with os=windows
|
||||
urlStr := fmt.Sprintf("/%s/%s/actions/run?workflow=%s", user2.Name, repo.Name, "test-inputs-context.yml")
|
||||
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
"ref": "refs/heads/main",
|
||||
"os": "windows",
|
||||
"ref": "refs/heads/main",
|
||||
"os": "windows",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
||||
"connectrpc.com/connect"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestJobWithNeeds(t *testing.T) {
|
||||
@@ -349,6 +350,122 @@ jobs:
|
||||
})
|
||||
}
|
||||
|
||||
func TestRunnerDisableEnable(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, _ *url.URL) {
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
session := loginUser(t, user2.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
|
||||
t.Run("BasicDisableEnable", func(t *testing.T) {
|
||||
testData := prepareRunnerDisableEnableTest(t, user2, token, "actions-runner-disable-enable", "mock-runner", "runner-disable-enable")
|
||||
|
||||
task1 := testData.runner.fetchTask(t)
|
||||
require.NotNil(t, task1)
|
||||
|
||||
triggerRunnerDisableEnableRun(t, user2, token, testData.repo, "second-push.txt")
|
||||
|
||||
req := newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/repos/%s/%s/actions/runners/%d", user2.Name, testData.repo.Name, testData.runnerID), true).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
testData.runner.execTask(t, task1, &mockTaskOutcome{result: runnerv1.Result_RESULT_SUCCESS})
|
||||
testData.runner.fetchNoTask(t, 2*time.Second)
|
||||
|
||||
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/repos/%s/%s/actions/runners/%d", user2.Name, testData.repo.Name, testData.runnerID), false).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
task2 := testData.runner.fetchTask(t, 5*time.Second)
|
||||
require.NotNil(t, task2)
|
||||
testData.runner.execTask(t, task2, &mockTaskOutcome{result: runnerv1.Result_RESULT_SUCCESS})
|
||||
})
|
||||
|
||||
t.Run("TasksVersionPath", func(t *testing.T) {
|
||||
testData := prepareRunnerDisableEnableTest(t, user2, token, "actions-runner-version-path", "mock-runner-version-path", "runner-version-path")
|
||||
|
||||
var firstVersion int64
|
||||
var task1 *runnerv1.Task
|
||||
ddl := time.Now().Add(5 * time.Second)
|
||||
for time.Now().Before(ddl) {
|
||||
task1, firstVersion = testData.runner.fetchTaskOnce(t, 0)
|
||||
if task1 != nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
require.NotNil(t, task1, "expected to receive first task")
|
||||
require.NotZero(t, firstVersion, "response TasksVersion should be set")
|
||||
|
||||
// Trigger a second run so there is a pending job after we re-enable the runner
|
||||
triggerRunnerDisableEnableRun(t, user2, token, testData.repo, "second-push.txt")
|
||||
time.Sleep(500 * time.Millisecond) // allow workflow run to be created
|
||||
|
||||
req := newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/repos/%s/%s/actions/runners/%d", user2.Name, testData.repo.Name, testData.runnerID), true).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
testData.runner.execTask(t, task1, &mockTaskOutcome{result: runnerv1.Result_RESULT_SUCCESS})
|
||||
|
||||
// Fetch with the version we had before disable. Server has bumped version on disable,
|
||||
// so we enter PickTask with a re-loaded runner (disabled) and get no task.
|
||||
taskAfterDisable, _ := testData.runner.fetchTaskOnce(t, firstVersion)
|
||||
assert.Nil(t, taskAfterDisable, "disabled runner must not receive a task when sending previous TasksVersion")
|
||||
|
||||
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/repos/%s/%s/actions/runners/%d", user2.Name, testData.repo.Name, testData.runnerID), false).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
task2 := testData.runner.fetchTask(t, 5*time.Second)
|
||||
require.NotNil(t, task2, "after re-enable runner should receive tasks again")
|
||||
testData.runner.execTask(t, task2, &mockTaskOutcome{result: runnerv1.Result_RESULT_SUCCESS})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type runnerDisableEnableTestData struct {
|
||||
repo *api.Repository
|
||||
runner *mockRunner
|
||||
runnerID int64
|
||||
}
|
||||
|
||||
func prepareRunnerDisableEnableTest(t *testing.T, user *user_model.User, authToken, repoName, runnerName, workflowName string) *runnerDisableEnableTestData {
|
||||
t.Helper()
|
||||
|
||||
apiRepo := createActionsTestRepo(t, authToken, repoName, false)
|
||||
runner := newMockRunner()
|
||||
runner.registerAsRepoRunner(t, user.Name, apiRepo.Name, runnerName, []string{"ubuntu-latest"}, false)
|
||||
|
||||
wfTreePath := fmt.Sprintf(".gitea/workflows/%s.yml", workflowName)
|
||||
wfContent := fmt.Sprintf(`name: %s
|
||||
on: [push]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo %s
|
||||
`, workflowName, workflowName)
|
||||
opts := getWorkflowCreateFileOptions(user, apiRepo.DefaultBranch, "create workflow", wfContent)
|
||||
createWorkflowFile(t, authToken, user.Name, apiRepo.Name, wfTreePath, opts)
|
||||
|
||||
return &runnerDisableEnableTestData{
|
||||
repo: apiRepo,
|
||||
runner: runner,
|
||||
runnerID: getRepoRunnerID(t, authToken, user.Name, apiRepo.Name),
|
||||
}
|
||||
}
|
||||
|
||||
func triggerRunnerDisableEnableRun(t *testing.T, user *user_model.User, authToken string, repo *api.Repository, treePath string) {
|
||||
t.Helper()
|
||||
opts := getWorkflowCreateFileOptions(user, repo.DefaultBranch, "second push", "second run")
|
||||
createWorkflowFile(t, authToken, user.Name, repo.Name, treePath, opts)
|
||||
}
|
||||
|
||||
func getRepoRunnerID(t *testing.T, authToken, ownerName, repoName string) int64 {
|
||||
t.Helper()
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/actions/runners", ownerName, repoName)).AddTokenAuth(authToken)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
runnerList := api.ActionRunnersResponse{}
|
||||
DecodeJSON(t, resp, &runnerList)
|
||||
require.Len(t, runnerList.Entries, 1)
|
||||
return runnerList.Entries[0].ID
|
||||
}
|
||||
|
||||
func TestActionsGiteaContext(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
|
||||
@@ -0,0 +1,476 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
org_model "code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
unit_model "code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/lfs"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestActionsJobTokenPermissiveAccess(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
isFork bool
|
||||
|
||||
ownerPermMode repo_model.ActionsTokenPermissionMode
|
||||
ownerMaxPerms map[unit_model.Type]perm.AccessMode
|
||||
|
||||
repoPermMode repo_model.ActionsTokenPermissionMode
|
||||
repoMaxPerms map[unit_model.Type]perm.AccessMode
|
||||
|
||||
expectGitAccess perm.AccessMode
|
||||
}{
|
||||
{
|
||||
name: "OwnerConfig-Permissive",
|
||||
ownerPermMode: repo_model.ActionsTokenPermissionModePermissive,
|
||||
expectGitAccess: perm.AccessModeWrite,
|
||||
},
|
||||
{
|
||||
name: "OwnerConfig-Permissive-CodeNone",
|
||||
ownerPermMode: repo_model.ActionsTokenPermissionModePermissive,
|
||||
ownerMaxPerms: map[unit_model.Type]perm.AccessMode{unit_model.TypeCode: perm.AccessModeNone},
|
||||
expectGitAccess: perm.AccessModeNone,
|
||||
},
|
||||
{
|
||||
name: "OwnerConfig-Restricted",
|
||||
ownerPermMode: repo_model.ActionsTokenPermissionModeRestricted,
|
||||
expectGitAccess: perm.AccessModeRead,
|
||||
},
|
||||
|
||||
// repo uses its own settings, so owner settings should not affect it
|
||||
{
|
||||
name: "SameRepo-Permissive",
|
||||
ownerPermMode: repo_model.ActionsTokenPermissionModeRestricted,
|
||||
ownerMaxPerms: map[unit_model.Type]perm.AccessMode{unit_model.TypeCode: perm.AccessModeNone},
|
||||
repoPermMode: repo_model.ActionsTokenPermissionModePermissive,
|
||||
expectGitAccess: perm.AccessModeWrite,
|
||||
},
|
||||
{
|
||||
name: "SameRepo-Permissive-CodeNone",
|
||||
ownerPermMode: repo_model.ActionsTokenPermissionModePermissive,
|
||||
ownerMaxPerms: map[unit_model.Type]perm.AccessMode{unit_model.TypeCode: perm.AccessModeRead},
|
||||
repoPermMode: repo_model.ActionsTokenPermissionModePermissive,
|
||||
repoMaxPerms: map[unit_model.Type]perm.AccessMode{unit_model.TypeCode: perm.AccessModeNone},
|
||||
expectGitAccess: perm.AccessModeNone,
|
||||
},
|
||||
{
|
||||
name: "SameRepo-Restricted",
|
||||
repoPermMode: repo_model.ActionsTokenPermissionModeRestricted,
|
||||
expectGitAccess: perm.AccessModeRead,
|
||||
},
|
||||
|
||||
// forks should be always restricted to max read access for code
|
||||
{
|
||||
name: "Fork-Permissive",
|
||||
repoPermMode: repo_model.ActionsTokenPermissionModePermissive,
|
||||
isFork: true,
|
||||
expectGitAccess: perm.AccessModeRead,
|
||||
},
|
||||
{
|
||||
name: "Fork-Restricted",
|
||||
repoPermMode: repo_model.ActionsTokenPermissionModeRestricted,
|
||||
isFork: true,
|
||||
expectGitAccess: perm.AccessModeRead,
|
||||
},
|
||||
{
|
||||
name: "Fork-Restricted-CodeNone",
|
||||
repoPermMode: repo_model.ActionsTokenPermissionModeRestricted,
|
||||
repoMaxPerms: map[unit_model.Type]perm.AccessMode{unit_model.TypeCode: perm.AccessModeNone},
|
||||
isFork: true,
|
||||
expectGitAccess: perm.AccessModeNone,
|
||||
},
|
||||
}
|
||||
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
task := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 47})
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: task.RepoID})
|
||||
repoActionsUnit := repo.MustGetUnit(t.Context(), unit_model.TypeActions)
|
||||
repoActionsCfg := repoActionsUnit.ActionsConfig()
|
||||
ownerActionsCfg, err := actions_model.GetOwnerActionsConfig(t.Context(), repo.OwnerID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = db.GetEngine(t.Context()).ID(task.RepoID).Cols("is_private").Update(&repo_model.Repository{IsPrivate: true})
|
||||
require.NoError(t, err)
|
||||
|
||||
assertRespCodeForSuccess := func(t *testing.T, resp *httptest.ResponseRecorder, succeed bool) {
|
||||
if succeed {
|
||||
assert.True(t, 200 <= resp.Code && resp.Code < 300, "Expected success status code, got %d", resp.Code)
|
||||
} else {
|
||||
assert.True(t, 400 <= resp.Code && resp.Code < 500, "Expected client error status code, got %d", resp.Code)
|
||||
}
|
||||
}
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// prepare owner's token permissions settings
|
||||
ownerActionsCfg.TokenPermissionMode = tt.ownerPermMode
|
||||
ownerActionsCfg.MaxTokenPermissions = util.Iif(tt.ownerMaxPerms == nil, nil, &repo_model.ActionsTokenPermissions{UnitAccessModes: tt.ownerMaxPerms})
|
||||
require.NoError(t, actions_model.SetOwnerActionsConfig(t.Context(), repo.OwnerID, ownerActionsCfg))
|
||||
|
||||
// prepare repo's token permissions settings
|
||||
repoActionsCfg.OverrideOwnerConfig = tt.repoPermMode != "" || tt.repoMaxPerms != nil
|
||||
repoActionsCfg.TokenPermissionMode = tt.repoPermMode
|
||||
repoActionsCfg.MaxTokenPermissions = util.Iif(tt.repoMaxPerms == nil, nil, &repo_model.ActionsTokenPermissions{UnitAccessModes: tt.repoMaxPerms})
|
||||
require.NoError(t, repo_model.UpdateRepoUnitConfig(t.Context(), repoActionsUnit))
|
||||
|
||||
// prepare task and its token
|
||||
require.NoError(t, task.GenerateToken())
|
||||
task.Status = actions_model.StatusRunning
|
||||
task.IsForkPullRequest = tt.isFork
|
||||
err := actions_model.UpdateTask(t.Context(), task, "token_hash", "token_salt", "token_last_eight", "status", "is_fork_pull_request")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, task.LoadJob(t.Context()))
|
||||
require.NoError(t, task.Job.LoadRun(t.Context()))
|
||||
task.Job.Run.IsForkPullRequest = tt.isFork
|
||||
require.NoError(t, actions_model.UpdateRun(t.Context(), task.Job.Run, "is_fork_pull_request"))
|
||||
|
||||
testURL := *u
|
||||
testURL.User = url.UserPassword("gitea-actions", task.Token)
|
||||
|
||||
t.Run("ReadGitContent", func(t *testing.T) {
|
||||
testURL.Path = "/user5/repo4.git/HEAD"
|
||||
resp := MakeRequest(t, NewRequest(t, "GET", testURL.String()), NoExpectedStatus)
|
||||
assertRespCodeForSuccess(t, resp, tt.expectGitAccess != perm.AccessModeNone)
|
||||
|
||||
testURL.Path = "/user5/repo4.git/info/lfs/locks"
|
||||
req := NewRequest(t, "GET", testURL.String()).SetHeader("Accept", lfs.MediaType)
|
||||
resp = MakeRequest(t, req, NoExpectedStatus)
|
||||
assertRespCodeForSuccess(t, resp, tt.expectGitAccess != perm.AccessModeNone)
|
||||
})
|
||||
|
||||
t.Run("WriteGitContent", func(t *testing.T) {
|
||||
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/contents/test-filename", repo.FullName()), &structs.CreateFileOptions{
|
||||
FileOptions: structs.FileOptions{NewBranchName: "new-branch" + t.Name()},
|
||||
ContentBase64: base64.StdEncoding.EncodeToString([]byte(`dummy content`)),
|
||||
}).AddTokenAuth(task.Token)
|
||||
resp := MakeRequest(t, req, NoExpectedStatus)
|
||||
assertRespCodeForSuccess(t, resp, tt.expectGitAccess == perm.AccessModeWrite)
|
||||
|
||||
testURL.Path = "/user5/repo4.git/info/lfs/objects/batch"
|
||||
req = NewRequestWithJSON(t, "POST", testURL.String(), lfs.BatchRequest{Operation: "upload"}).SetHeader("Accept", lfs.MediaType)
|
||||
resp = MakeRequest(t, req, NoExpectedStatus)
|
||||
assertRespCodeForSuccess(t, resp, tt.expectGitAccess == perm.AccessModeWrite)
|
||||
})
|
||||
|
||||
t.Run("NoOtherPermissions", func(t *testing.T) {
|
||||
req := NewRequest(t, "DELETE", "/api/v1/repos/"+repo.FullName()).AddTokenAuth(task.Token)
|
||||
resp := MakeRequest(t, req, NoExpectedStatus)
|
||||
assertRespCodeForSuccess(t, resp, false)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestActionsCrossRepoAccess(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
session := loginUser(t, "user2")
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteOrganization)
|
||||
|
||||
// 1. Create Organization
|
||||
orgName := "org-cross-test"
|
||||
req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &structs.CreateOrgOption{
|
||||
UserName: orgName,
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
owner, err := org_model.GetOrgByName(t.Context(), orgName)
|
||||
require.NoError(t, err)
|
||||
|
||||
// 2. Create Two Repositories in owner
|
||||
createRepoInOrg := func(name string) int64 {
|
||||
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/repos", orgName), &structs.CreateRepoOption{
|
||||
Name: name,
|
||||
AutoInit: true,
|
||||
}).AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusCreated)
|
||||
var repo structs.Repository
|
||||
DecodeJSON(t, resp, &repo)
|
||||
return repo.ID
|
||||
}
|
||||
|
||||
repoAID := createRepoInOrg("repo-A")
|
||||
repoBID := createRepoInOrg("repo-B")
|
||||
|
||||
// 3. Enable Actions in Repo A (Source) and Repo B (Target)
|
||||
enableActions := func(repoID int64) {
|
||||
err := db.Insert(t.Context(), &repo_model.RepoUnit{
|
||||
RepoID: repoID,
|
||||
Type: unit_model.TypeActions,
|
||||
Config: &repo_model.ActionsConfig{
|
||||
TokenPermissionMode: repo_model.ActionsTokenPermissionModePermissive,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
enableActions(repoAID)
|
||||
enableActions(repoBID)
|
||||
|
||||
// 4. Create Task in Repo A, and use A's token to access B
|
||||
taskA := createActionTask(t, repoAID, false)
|
||||
testCtxA := APITestContext{
|
||||
Session: emptyTestSession(t),
|
||||
Token: taskA.Token,
|
||||
Username: orgName,
|
||||
Reponame: "repo-B",
|
||||
}
|
||||
|
||||
testCtxA.ExpectedCode = http.StatusOK
|
||||
t.Run("PublicCrossRepoAccess", doAPIGetRepository(testCtxA, func(t *testing.T, r structs.Repository) {
|
||||
assert.Equal(t, "repo-B", r.Name)
|
||||
}))
|
||||
|
||||
// make repo-B be private
|
||||
req = NewRequestWithJSON(t, "PATCH", "/api/v1/repos/org-cross-test/repo-B", &structs.EditRepoOption{Private: new(true)}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
testCtxA.ExpectedCode = http.StatusNotFound
|
||||
t.Run("NoPrivateCrossRepoAccess", doAPIGetRepository(testCtxA, nil))
|
||||
|
||||
ownerActionsCfg := actions_model.OwnerActionsConfig{AllowedCrossRepoIDs: []int64{repoBID}}
|
||||
require.NoError(t, actions_model.SetOwnerActionsConfig(t.Context(), owner.ID, ownerActionsCfg))
|
||||
|
||||
testCtxA.ExpectedCode = http.StatusOK
|
||||
t.Run("AccessToSelectedPrivateRepo", doAPIGetRepository(testCtxA, func(t *testing.T, r structs.Repository) {
|
||||
assert.Equal(t, "repo-B", r.Name)
|
||||
}))
|
||||
|
||||
t.Run("RepoTransfer", func(t *testing.T) {
|
||||
ownerActionsCfg, err := actions_model.GetOwnerActionsConfig(t.Context(), owner.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, ownerActionsCfg.AllowedCrossRepoIDs, repoBID)
|
||||
|
||||
// Transfer Repository to user4
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/repo-B/transfer", orgName), &structs.TransferRepoOption{
|
||||
NewOwner: "user4",
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
// Accept transfer as user4
|
||||
session4 := loginUser(t, "user4")
|
||||
token4 := getTokenForLoggedInUser(t, session4, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository)
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/repo-B/transfer/accept", orgName)).AddTokenAuth(token4)
|
||||
MakeRequest(t, req, http.StatusAccepted)
|
||||
|
||||
// Verify it is removed from the org's config
|
||||
ownerActionsCfg, err = actions_model.GetOwnerActionsConfig(t.Context(), owner.ID)
|
||||
require.NoError(t, err)
|
||||
assert.NotContains(t, ownerActionsCfg.AllowedCrossRepoIDs, repoBID)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestActionsJobTokenPermissions(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
t.Run("WriteIssue", TestActionsJobTokenPermissionsWriteIssue)
|
||||
}
|
||||
|
||||
func TestActionsJobTokenPermissionsWriteIssue(t *testing.T) {
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||
task := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 53})
|
||||
require.Equal(t, repo.ID, task.RepoID)
|
||||
|
||||
require.NoError(t, db.Insert(t.Context(), &repo_model.RepoUnit{
|
||||
RepoID: repo.ID,
|
||||
Type: unit_model.TypeActions,
|
||||
Config: &repo_model.ActionsConfig{},
|
||||
}))
|
||||
|
||||
repoActionsUnit := repo.MustGetUnit(t.Context(), unit_model.TypeActions)
|
||||
repoActionsCfg := repoActionsUnit.ActionsConfig()
|
||||
repoActionsCfg.OverrideOwnerConfig = true
|
||||
repoActionsCfg.TokenPermissionMode = repo_model.ActionsTokenPermissionModePermissive
|
||||
repoActionsCfg.MaxTokenPermissions = nil
|
||||
require.NoError(t, repo_model.UpdateRepoUnitConfig(t.Context(), repoActionsUnit))
|
||||
|
||||
require.NoError(t, task.GenerateToken())
|
||||
task.Status = actions_model.StatusRunning
|
||||
require.NoError(t, actions_model.UpdateTask(t.Context(), task, "token_hash", "token_salt", "token_last_eight", "status"))
|
||||
|
||||
session := loginUser(t, user.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
labelURL := fmt.Sprintf("/api/v1/repos/%s/%s/labels", user.Name, repo.Name)
|
||||
req := NewRequestWithJSON(t, "POST", labelURL, &structs.CreateLabelOption{
|
||||
Name: "task-label",
|
||||
Color: "0e8a16",
|
||||
}).AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusCreated)
|
||||
label := DecodeJSON(t, resp, &structs.Label{})
|
||||
|
||||
issueURL := fmt.Sprintf("/api/v1/repos/%s/%s/issues", user.Name, repo.Name)
|
||||
req = NewRequestWithJSON(t, "POST", issueURL, &structs.CreateIssueOption{
|
||||
Title: "issue for actions token label deletion",
|
||||
}).AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusCreated)
|
||||
issue := DecodeJSON(t, resp, &structs.Issue{})
|
||||
|
||||
taskToken := task.Token
|
||||
require.NotEmpty(t, taskToken)
|
||||
|
||||
issueLabelsURL := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels", user.Name, repo.Name, issue.Index)
|
||||
req = NewRequestWithJSON(t, "POST", issueLabelsURL, &structs.IssueLabelsOption{
|
||||
Labels: []any{label.ID},
|
||||
}).AddTokenAuth(taskToken)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%d", issueLabelsURL, label.ID)).AddTokenAuth(taskToken)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
}
|
||||
|
||||
func createActionTask(t *testing.T, repoID int64, isFork bool) *actions_model.ActionTask {
|
||||
job := &actions_model.ActionRunJob{
|
||||
RepoID: repoID,
|
||||
Status: actions_model.StatusRunning,
|
||||
IsForkPullRequest: isFork,
|
||||
JobID: "test_job",
|
||||
Name: "test_job",
|
||||
}
|
||||
require.NoError(t, db.Insert(t.Context(), job))
|
||||
task := &actions_model.ActionTask{
|
||||
JobID: job.ID,
|
||||
RepoID: repoID,
|
||||
Status: actions_model.StatusRunning,
|
||||
IsForkPullRequest: isFork,
|
||||
}
|
||||
require.NoError(t, task.GenerateToken())
|
||||
require.NoError(t, db.Insert(t.Context(), task))
|
||||
return task
|
||||
}
|
||||
|
||||
func TestActionsTokenPermissionsPersistenceWithWorkflow(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
session := loginUser(t, user2.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
|
||||
// create repos
|
||||
repo1 := createActionsTestRepo(t, token, "actions-permission-repo1", false)
|
||||
repo2 := createActionsTestRepo(t, token, "actions-permission-repo2", true)
|
||||
|
||||
// add repo2 to owner-level cross-repo access list
|
||||
req := NewRequestWithValues(t, "POST", "/user/settings/actions/general", map[string]string{
|
||||
"cross_repo_add_target": "true",
|
||||
"cross_repo_add_target_name": repo2.Name,
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// create the runner for repo1
|
||||
runner1 := newMockRunner()
|
||||
runner1.registerAsRepoRunner(t, user2.Name, repo1.Name, "mock-runner", []string{"ubuntu-latest"}, false)
|
||||
|
||||
// set repo1 actions token permission mode to "permissive"
|
||||
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo1.Name), map[string]string{
|
||||
"token_permission_mode": "permissive",
|
||||
"override_owner_config": "true",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
// set repo2 actions token permission mode to "restricted", and set max permissions
|
||||
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo2.Name), map[string]string{
|
||||
"token_permission_mode": "restricted",
|
||||
"override_owner_config": "true",
|
||||
"enable_max_permissions": "true",
|
||||
"max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeReleases)): "read",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
// create a workflow file with "permission" keyword for repo1
|
||||
wfTreePath := ".gitea/workflows/test_permissions.yml"
|
||||
wfFileContent := `name: Test Permissions
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.gitea/workflows/test_permissions.yml'
|
||||
|
||||
jobs:
|
||||
job-override:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
code: write
|
||||
steps:
|
||||
- run: echo "test perms"
|
||||
`
|
||||
opts := getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+wfTreePath, wfFileContent)
|
||||
createWorkflowFile(t, token, user2.Name, repo1.Name, wfTreePath, opts)
|
||||
|
||||
task1 := runner1.fetchTask(t)
|
||||
task1Token := task1.Secrets["GITEA_TOKEN"]
|
||||
require.NotEmpty(t, task1Token)
|
||||
|
||||
// should fail: target repo does not allow code access
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo2.Name)).AddTokenAuth(task1Token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
// set repo2 max permission to "read" so that the actions token can access code
|
||||
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo2.Name), map[string]string{
|
||||
"token_permission_mode": "restricted",
|
||||
"override_owner_config": "true",
|
||||
"enable_max_permissions": "true",
|
||||
"max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeCode)): "read",
|
||||
"max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeReleases)): "read",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
// should succeed: target repo now allows code read access for this token
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo2.Name)).AddTokenAuth(task1Token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
// but it should not have write access
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s/%s.git/info/lfs/objects/batch", user2.Name, repo2.Name), lfs.BatchRequest{Operation: "upload"}).
|
||||
SetHeader("Accept", lfs.MediaType).
|
||||
AddBasicAuth("gitea-actions", task1Token)
|
||||
MakeRequest(t, req, http.StatusUnauthorized)
|
||||
|
||||
// set repo1&repo2 max permission to "write" so that the actions token can access code
|
||||
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo1.Name), map[string]string{
|
||||
"token_permission_mode": "restricted",
|
||||
"override_owner_config": "true",
|
||||
"enable_max_permissions": "true",
|
||||
"max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeCode)): "write",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo2.Name), map[string]string{
|
||||
"token_permission_mode": "restricted",
|
||||
"override_owner_config": "true",
|
||||
"enable_max_permissions": "true",
|
||||
"max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeCode)): "write",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
// now task1 has write access to repo1, but still only read access to repo2 (different repo)
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s/%s.git/info/lfs/objects/batch", user2.Name, repo1.Name), lfs.BatchRequest{Operation: "upload"}).
|
||||
SetHeader("Accept", lfs.MediaType).
|
||||
AddBasicAuth("gitea-actions", task1Token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s/%s.git/info/lfs/objects/batch", user2.Name, repo2.Name), lfs.BatchRequest{Operation: "upload"}).
|
||||
SetHeader("Accept", lfs.MediaType).
|
||||
AddBasicAuth("gitea-actions", task1Token)
|
||||
MakeRequest(t, req, http.StatusUnauthorized)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/dbfs"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
actions_module "code.gitea.io/gitea/modules/actions"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
|
||||
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
||||
"connectrpc.com/connect"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Regression for https://gitea.com/gitea/runner/issues/950: a runner that
|
||||
// finalizes a task with no log output sends UpdateLog{Rows:[], NoMore:true}.
|
||||
// The previous short-circuit on len(Rows)==0 skipped TransferLogs, leaving
|
||||
// an orphan dbfs_data row. Verify the row is now archived and removed.
|
||||
func TestActionsLogFinalizeWithoutRows(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, _ *url.URL) {
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
session := loginUser(t, user2.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
|
||||
apiRepo := createActionsTestRepo(t, token, "actions-finalize-no-rows", false)
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
|
||||
|
||||
runner := newMockRunner()
|
||||
runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false)
|
||||
|
||||
const wfTreePath = ".gitea/workflows/finalize-no-rows.yml"
|
||||
wfFileContent := fmt.Sprintf(`name: finalize-no-rows
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '%s'
|
||||
jobs:
|
||||
job1:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: noop
|
||||
`, wfTreePath)
|
||||
createWorkflowFile(t, token, user2.Name, repo.Name, wfTreePath, getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "trigger", wfFileContent))
|
||||
|
||||
task := runner.fetchTask(t)
|
||||
|
||||
resp, err := runner.client.runnerServiceClient.UpdateLog(t.Context(), connect.NewRequest(&runnerv1.UpdateLogRequest{
|
||||
TaskId: task.Id,
|
||||
Index: 0,
|
||||
Rows: nil,
|
||||
NoMore: true,
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, 0, resp.Msg.AckIndex)
|
||||
|
||||
freshTask := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: task.Id})
|
||||
require.True(t, freshTask.LogInStorage, "log_in_storage must flip after empty NoMore=true")
|
||||
|
||||
_, err = storage.Actions.Stat(freshTask.LogFilename)
|
||||
assert.NoError(t, err, "archived log must exist in storage")
|
||||
|
||||
_, err = dbfs.Open(t.Context(), actions_module.DBFSPrefix+freshTask.LogFilename)
|
||||
assert.ErrorIs(t, err, os.ErrNotExist, "DBFS row must be cleaned up after TransferLogs")
|
||||
})
|
||||
}
|
||||
@@ -7,12 +7,10 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
@@ -171,7 +169,7 @@ jobs:
|
||||
createWorkflowFile(t, token, user2.Name, repo.Name, tc.treePath, opts)
|
||||
|
||||
// fetch and execute tasks
|
||||
for jobIndex, outcome := range tc.outcome {
|
||||
for _, outcome := range tc.outcome {
|
||||
task := runner.fetchTask(t)
|
||||
runner.execTask(t, task, outcome)
|
||||
|
||||
@@ -183,9 +181,10 @@ jobs:
|
||||
_, err := storage.Actions.Stat(logFileName)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, job, run := getTaskAndJobAndRunByTaskID(t, task.Id)
|
||||
|
||||
// download task logs and check content
|
||||
runIndex := task.Context.GetFields()["run_number"].GetStringValue()
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%s/jobs/%d/logs", user2.Name, repo.Name, runIndex, jobIndex)).
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/logs", user2.Name, repo.Name, run.ID, job.ID)).
|
||||
AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
logTextLines := strings.Split(strings.TrimSpace(resp.Body.String()), "\n")
|
||||
@@ -198,15 +197,8 @@ jobs:
|
||||
)
|
||||
}
|
||||
|
||||
runID, _ := strconv.ParseInt(task.Context.GetFields()["run_id"].GetStringValue(), 10, 64)
|
||||
|
||||
jobs, err := actions_model.GetRunJobsByRunID(t.Context(), runID)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, jobs, len(tc.outcome))
|
||||
jobID := jobs[jobIndex].ID
|
||||
|
||||
// download task logs from API and check content
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/actions/jobs/%d/logs", user2.Name, repo.Name, jobID)).
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/actions/jobs/%d/logs", user2.Name, repo.Name, job.ID)).
|
||||
AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
logTextLines = strings.Split(strings.TrimSpace(resp.Body.String()), "\n")
|
||||
|
||||
@@ -33,7 +33,7 @@ func TestActionsRerun(t *testing.T) {
|
||||
|
||||
wfTreePath := ".gitea/workflows/actions-rerun-workflow-1.yml"
|
||||
wfFileContent := `name: actions-rerun-workflow-1
|
||||
on:
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.gitea/workflows/actions-rerun-workflow-1.yml'
|
||||
@@ -54,25 +54,22 @@ jobs:
|
||||
|
||||
// fetch and exec job1
|
||||
job1Task := runner.fetchTask(t)
|
||||
_, _, run := getTaskAndJobAndRunByTaskID(t, job1Task.Id)
|
||||
_, job1, run := getTaskAndJobAndRunByTaskID(t, job1Task.Id)
|
||||
runner.execTask(t, job1Task, &mockTaskOutcome{
|
||||
result: runnerv1.Result_RESULT_SUCCESS,
|
||||
})
|
||||
// RERUN-FAILURE: the run is not done
|
||||
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, repo.Name, run.Index), map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
})
|
||||
req := NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, repo.Name, run.ID))
|
||||
session.MakeRequest(t, req, http.StatusBadRequest)
|
||||
// fetch and exec job2
|
||||
job2Task := runner.fetchTask(t)
|
||||
_, job2, _ := getTaskAndJobAndRunByTaskID(t, job2Task.Id)
|
||||
runner.execTask(t, job2Task, &mockTaskOutcome{
|
||||
result: runnerv1.Result_RESULT_SUCCESS,
|
||||
})
|
||||
|
||||
// RERUN-1: rerun the run
|
||||
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, repo.Name, run.Index), map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
})
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, repo.Name, run.ID))
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
// fetch and exec job1
|
||||
job1TaskR1 := runner.fetchTask(t)
|
||||
@@ -86,9 +83,7 @@ jobs:
|
||||
})
|
||||
|
||||
// RERUN-2: rerun job1
|
||||
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, repo.Name, run.Index, 0), map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
})
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, repo.Name, run.ID, job1.ID))
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
// job2 needs job1, so rerunning job1 will also rerun job2
|
||||
// fetch and exec job1
|
||||
@@ -103,9 +98,7 @@ jobs:
|
||||
})
|
||||
|
||||
// RERUN-3: rerun job2
|
||||
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, repo.Name, run.Index, 1), map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
})
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, repo.Name, run.ID, job2.ID))
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
// only job2 will rerun
|
||||
// fetch and exec job2
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
actions_web "code.gitea.io/gitea/routers/web/repo/actions"
|
||||
|
||||
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestActionsRoute(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
t.Run("testActionsRouteForIDBasedURL", testActionsRouteForIDBasedURL)
|
||||
t.Run("testActionsRouteForLegacyIndexBasedURL", testActionsRouteForLegacyIndexBasedURL)
|
||||
})
|
||||
}
|
||||
|
||||
func testActionsRouteForIDBasedURL(t *testing.T) {
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
user2Session := loginUser(t, user2.Name)
|
||||
user2Token := getTokenForLoggedInUser(t, user2Session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
|
||||
repo1 := createActionsTestRepo(t, user2Token, "actions-route-id-url-1", false)
|
||||
runner1 := newMockRunner()
|
||||
runner1.registerAsRepoRunner(t, user2.Name, repo1.Name, "mock-runner", []string{"ubuntu-latest"}, false)
|
||||
repo2 := createActionsTestRepo(t, user2Token, "actions-route-id-url-2", false)
|
||||
runner2 := newMockRunner()
|
||||
runner2.registerAsRepoRunner(t, user2.Name, repo2.Name, "mock-runner", []string{"ubuntu-latest"}, false)
|
||||
|
||||
workflowTreePath := ".gitea/workflows/test.yml"
|
||||
workflowContent := `name: test
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.gitea/workflows/test.yml'
|
||||
jobs:
|
||||
job1:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo job1
|
||||
`
|
||||
|
||||
opts := getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+workflowTreePath, workflowContent)
|
||||
createWorkflowFile(t, user2Token, user2.Name, repo1.Name, workflowTreePath, opts)
|
||||
createWorkflowFile(t, user2Token, user2.Name, repo2.Name, workflowTreePath, opts)
|
||||
|
||||
task1 := runner1.fetchTask(t)
|
||||
_, job1, run1 := getTaskAndJobAndRunByTaskID(t, task1.Id)
|
||||
task2 := runner2.fetchTask(t)
|
||||
_, job2, run2 := getTaskAndJobAndRunByTaskID(t, task2.Id)
|
||||
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d", user2.Name, repo1.Name, run1.ID))
|
||||
user2Session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d", user2.Name, repo1.Name, 999999))
|
||||
user2Session.MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
// run1 and job1 belong to repo1, success
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d", user2.Name, repo1.Name, run1.ID, job1.ID))
|
||||
resp := user2Session.MakeRequest(t, req, http.StatusOK)
|
||||
var viewResp actions_web.ViewResponse
|
||||
DecodeJSON(t, resp, &viewResp)
|
||||
assert.Len(t, viewResp.State.Run.Jobs, 1)
|
||||
assert.Equal(t, job1.ID, viewResp.State.Run.Jobs[0].ID)
|
||||
|
||||
// run2 and job2 do not belong to repo1, failure
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d", user2.Name, repo1.Name, run2.ID, job2.ID))
|
||||
user2Session.MakeRequest(t, req, http.StatusNotFound)
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d", user2.Name, repo1.Name, run1.ID, job2.ID))
|
||||
user2Session.MakeRequest(t, req, http.StatusNotFound)
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d", user2.Name, repo1.Name, run2.ID, job1.ID))
|
||||
user2Session.MakeRequest(t, req, http.StatusNotFound)
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d/workflow", user2.Name, repo1.Name, run2.ID))
|
||||
user2Session.MakeRequest(t, req, http.StatusNotFound)
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/approve", user2.Name, repo1.Name, run2.ID))
|
||||
user2Session.MakeRequest(t, req, http.StatusNotFound)
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/cancel", user2.Name, repo1.Name, run2.ID))
|
||||
user2Session.MakeRequest(t, req, http.StatusNotFound)
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/delete", user2.Name, repo1.Name, run2.ID))
|
||||
user2Session.MakeRequest(t, req, http.StatusNotFound)
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d/artifacts/test.txt", user2.Name, repo1.Name, run2.ID))
|
||||
user2Session.MakeRequest(t, req, http.StatusNotFound)
|
||||
req = NewRequest(t, "DELETE", fmt.Sprintf("/%s/%s/actions/runs/%d/artifacts/test.txt", user2.Name, repo1.Name, run2.ID))
|
||||
user2Session.MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
// make the tasks complete, then test rerun
|
||||
runner1.execTask(t, task1, &mockTaskOutcome{
|
||||
result: runnerv1.Result_RESULT_SUCCESS,
|
||||
})
|
||||
runner2.execTask(t, task2, &mockTaskOutcome{
|
||||
result: runnerv1.Result_RESULT_SUCCESS,
|
||||
})
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, repo1.Name, run2.ID))
|
||||
user2Session.MakeRequest(t, req, http.StatusNotFound)
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, repo1.Name, run2.ID, job2.ID))
|
||||
user2Session.MakeRequest(t, req, http.StatusNotFound)
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, repo1.Name, run1.ID, job2.ID))
|
||||
user2Session.MakeRequest(t, req, http.StatusNotFound)
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, repo1.Name, run2.ID, job1.ID))
|
||||
user2Session.MakeRequest(t, req, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func testActionsRouteForLegacyIndexBasedURL(t *testing.T) {
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
user2Session := loginUser(t, user2.Name)
|
||||
user2Token := getTokenForLoggedInUser(t, user2Session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
repo := createActionsTestRepo(t, user2Token, "actions-route-legacy-url", false)
|
||||
|
||||
mkRun := func(id, index int64, title, sha string) *actions_model.ActionRun {
|
||||
return &actions_model.ActionRun{
|
||||
ID: id,
|
||||
Index: index,
|
||||
RepoID: repo.ID,
|
||||
OwnerID: user2.ID,
|
||||
Title: title,
|
||||
WorkflowID: "legacy-route.yml",
|
||||
TriggerUserID: user2.ID,
|
||||
Ref: "refs/heads/master",
|
||||
CommitSHA: sha,
|
||||
Status: actions_model.StatusWaiting,
|
||||
}
|
||||
}
|
||||
mkJob := func(id, runID int64, name, sha string) *actions_model.ActionRunJob {
|
||||
return &actions_model.ActionRunJob{
|
||||
ID: id,
|
||||
RunID: runID,
|
||||
RepoID: repo.ID,
|
||||
OwnerID: user2.ID,
|
||||
CommitSHA: sha,
|
||||
Name: name,
|
||||
Status: actions_model.StatusWaiting,
|
||||
}
|
||||
}
|
||||
|
||||
// A small ID-based run/job pair that should always resolve directly.
|
||||
smallIDRun := mkRun(80, 20, "legacy route small id", "aaa001")
|
||||
smallIDJob := mkJob(170, smallIDRun.ID, "legacy-small-job", smallIDRun.CommitSHA)
|
||||
// Another small run used to provide a job ID that belongs to a different run.
|
||||
otherSmallRun := mkRun(90, 30, "legacy route other small", "aaa002")
|
||||
otherSmallJob := mkJob(180, otherSmallRun.ID, "legacy-other-small-job", otherSmallRun.CommitSHA)
|
||||
|
||||
// A large-ID run whose legacy run index should redirect to its ID-based URL.
|
||||
normalRun := mkRun(1500, 900, "legacy route normal", "aaa003")
|
||||
normalRunJob := mkJob(1600, normalRun.ID, "legacy-normal-job", normalRun.CommitSHA)
|
||||
// A run whose index collides with normalRun.ID to exercise summary-page ID-first behavior.
|
||||
collisionRun := mkRun(2400, 1500, "legacy route collision", "aaa004")
|
||||
collisionJobIdx0 := mkJob(2600, collisionRun.ID, "legacy-collision-job-1", collisionRun.CommitSHA)
|
||||
collisionJobIdx1 := mkJob(2601, collisionRun.ID, "legacy-collision-job-2", collisionRun.CommitSHA)
|
||||
|
||||
// A run whose job has a smaller ID than the run itself (job_id < run_id)
|
||||
jobSmallerThanRunRun := mkRun(5000, 5500, "legacy route job before run", "aaa007")
|
||||
jobSmallerThanRunJob := mkJob(4500, jobSmallerThanRunRun.ID, "legacy-job-before-run-job", jobSmallerThanRunRun.CommitSHA)
|
||||
|
||||
// A small ID-based run/job pair that collides with a different legacy run/job index pair.
|
||||
ambiguousIDRun := mkRun(3, 1, "legacy route ambiguous id", "aaa005")
|
||||
ambiguousIDJob := mkJob(4, ambiguousIDRun.ID, "legacy-ambiguous-id-job", ambiguousIDRun.CommitSHA)
|
||||
// The legacy run/job target for the ambiguous /runs/3/jobs/4 URL.
|
||||
ambiguousLegacyRun := mkRun(1501, ambiguousIDRun.ID, "legacy route ambiguous legacy", "aaa006")
|
||||
ambiguousLegacyJobIdx0 := mkJob(1601, ambiguousLegacyRun.ID, "legacy-ambiguous-legacy-job-0", ambiguousLegacyRun.CommitSHA)
|
||||
ambiguousLegacyJobIdx1 := mkJob(1602, ambiguousLegacyRun.ID, "legacy-ambiguous-legacy-job-1", ambiguousLegacyRun.CommitSHA)
|
||||
ambiguousLegacyJobIdx2 := mkJob(1603, ambiguousLegacyRun.ID, "legacy-ambiguous-legacy-job-2", ambiguousLegacyRun.CommitSHA)
|
||||
ambiguousLegacyJobIdx3 := mkJob(1604, ambiguousLegacyRun.ID, "legacy-ambiguous-legacy-job-3", ambiguousLegacyRun.CommitSHA)
|
||||
ambiguousLegacyJobIdx4 := mkJob(1605, ambiguousLegacyRun.ID, "legacy-ambiguous-legacy-job-4", ambiguousLegacyRun.CommitSHA) // job_index=4
|
||||
ambiguousLegacyJobIdx5 := mkJob(1606, ambiguousLegacyRun.ID, "legacy-ambiguous-legacy-job-5", ambiguousLegacyRun.CommitSHA)
|
||||
ambiguousLegacyJobs := []*actions_model.ActionRunJob{
|
||||
ambiguousLegacyJobIdx0,
|
||||
ambiguousLegacyJobIdx1,
|
||||
ambiguousLegacyJobIdx2,
|
||||
ambiguousLegacyJobIdx3,
|
||||
ambiguousLegacyJobIdx4,
|
||||
ambiguousLegacyJobIdx5,
|
||||
}
|
||||
targetAmbiguousLegacyJob := ambiguousLegacyJobs[int(ambiguousIDJob.ID)]
|
||||
|
||||
insertBeansWithExplicitIDs(t, "action_run",
|
||||
smallIDRun, otherSmallRun, normalRun, ambiguousIDRun, ambiguousLegacyRun, collisionRun, jobSmallerThanRunRun,
|
||||
)
|
||||
insertBeansWithExplicitIDs(t, "action_run_job",
|
||||
smallIDJob, otherSmallJob, normalRunJob, ambiguousIDJob, collisionJobIdx0, collisionJobIdx1,
|
||||
ambiguousLegacyJobIdx0, ambiguousLegacyJobIdx1, ambiguousLegacyJobIdx2, ambiguousLegacyJobIdx3, ambiguousLegacyJobIdx4, ambiguousLegacyJobIdx5,
|
||||
jobSmallerThanRunJob,
|
||||
)
|
||||
|
||||
t.Run("OnlyRunID", func(t *testing.T) {
|
||||
// ID-based URLs must be valid
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d", user2.Name, repo.Name, smallIDRun.ID))
|
||||
user2Session.MakeRequest(t, req, http.StatusOK)
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d", user2.Name, repo.Name, normalRun.ID))
|
||||
user2Session.MakeRequest(t, req, http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("OnlyRunIndex", func(t *testing.T) {
|
||||
// legacy run index should redirect to the ID-based URL
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d", user2.Name, repo.Name, normalRun.Index))
|
||||
resp := user2Session.MakeRequest(t, req, http.StatusFound)
|
||||
assert.Equal(t, fmt.Sprintf("/%s/%s/actions/runs/%d", user2.Name, repo.Name, normalRun.ID), resp.Header().Get("Location"))
|
||||
|
||||
// Best-effort compatibility prefers the run ID when the same number also exists as a legacy run index.
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d", user2.Name, repo.Name, collisionRun.Index))
|
||||
resp = user2Session.MakeRequest(t, req, http.StatusOK)
|
||||
assert.Contains(t, resp.Body.String(), fmt.Sprintf(`data-run-id="%d"`, normalRun.ID)) // because collisionRun.Index == normalRun.ID
|
||||
|
||||
// by_index=1 should force the summary page to use the legacy run index interpretation.
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d?by_index=1", user2.Name, repo.Name, collisionRun.Index))
|
||||
resp = user2Session.MakeRequest(t, req, http.StatusFound)
|
||||
assert.Equal(t, fmt.Sprintf("/%s/%s/actions/runs/%d", user2.Name, repo.Name, collisionRun.ID), resp.Header().Get("Location"))
|
||||
})
|
||||
|
||||
t.Run("RunIDAndJobID", func(t *testing.T) {
|
||||
// ID-based URLs must be valid
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d", user2.Name, repo.Name, smallIDRun.ID, smallIDJob.ID))
|
||||
user2Session.MakeRequest(t, req, http.StatusOK)
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d", user2.Name, repo.Name, normalRun.ID, normalRunJob.ID))
|
||||
user2Session.MakeRequest(t, req, http.StatusOK)
|
||||
// URL must resolve even when job_id < run_id.
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d", user2.Name, repo.Name, jobSmallerThanRunRun.ID, jobSmallerThanRunJob.ID))
|
||||
user2Session.MakeRequest(t, req, http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("RunIndexAndJobIndex", func(t *testing.T) {
|
||||
// /user2/repo2/actions/runs/3/jobs/4 is ambiguous:
|
||||
// - it may resolve as the ID-based URL for run_id=3/job_id=4,
|
||||
// - or as the legacy index-based URL for run_index=3/job_index=4 which should redirect to run_id=1501/job_id=1605.
|
||||
idBasedURL := fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d", user2.Name, repo.Name, ambiguousIDRun.ID, ambiguousIDJob.ID)
|
||||
indexBasedURL := fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d", user2.Name, repo.Name, ambiguousLegacyRun.Index, 4) // for ambiguousLegacyJobIdx4
|
||||
assert.Equal(t, idBasedURL, indexBasedURL)
|
||||
// When both interpretations are valid, prefer the ID-based target by default.
|
||||
req := NewRequest(t, "GET", indexBasedURL)
|
||||
user2Session.MakeRequest(t, req, http.StatusOK)
|
||||
redirectURL := fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d", user2.Name, repo.Name, ambiguousLegacyRun.ID, targetAmbiguousLegacyJob.ID)
|
||||
// by_index=1 should explicitly force the legacy run/job index interpretation.
|
||||
req = NewRequest(t, "GET", indexBasedURL+"?by_index=1")
|
||||
resp := user2Session.MakeRequest(t, req, http.StatusFound)
|
||||
assert.Equal(t, redirectURL, resp.Header().Get("Location"))
|
||||
|
||||
// legacy job index 0 should redirect to the first job's ID
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/0", user2.Name, repo.Name, collisionRun.Index))
|
||||
resp = user2Session.MakeRequest(t, req, http.StatusFound)
|
||||
assert.Equal(t, fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d", user2.Name, repo.Name, collisionRun.ID, collisionJobIdx0.ID), resp.Header().Get("Location"))
|
||||
|
||||
// legacy job index 1 should redirect to the second job's ID
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/1", user2.Name, repo.Name, collisionRun.Index))
|
||||
resp = user2Session.MakeRequest(t, req, http.StatusFound)
|
||||
assert.Equal(t, fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d", user2.Name, repo.Name, collisionRun.ID, collisionJobIdx1.ID), resp.Header().Get("Location"))
|
||||
})
|
||||
|
||||
t.Run("InvalidURLs", func(t *testing.T) {
|
||||
// the job ID from a different run should not match
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d", user2.Name, repo.Name, smallIDRun.ID, otherSmallJob.ID))
|
||||
user2Session.MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
// resolve the run by index first and then return not found because the job index is out-of-range
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/2", user2.Name, repo.Name, normalRun.ID))
|
||||
user2Session.MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
// an out-of-range job index should return not found
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/2", user2.Name, repo.Name, collisionRun.Index))
|
||||
user2Session.MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
// a missing run number should return not found
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d", user2.Name, repo.Name, 999999))
|
||||
user2Session.MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
// a missing legacy run index should return not found
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/0", user2.Name, repo.Name, 999999))
|
||||
user2Session.MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
func insertBeansWithExplicitIDs(t *testing.T, table string, beans ...any) {
|
||||
t.Helper()
|
||||
ctx, committer, err := db.TxContext(t.Context())
|
||||
require.NoError(t, err)
|
||||
defer committer.Close()
|
||||
|
||||
if setting.Database.Type.IsMSSQL() {
|
||||
_, err = db.Exec(ctx, fmt.Sprintf("SET IDENTITY_INSERT [%s] ON", table))
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_, err = db.Exec(ctx, fmt.Sprintf("SET IDENTITY_INSERT [%s] OFF", table))
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
}
|
||||
require.NoError(t, db.Insert(ctx, beans...))
|
||||
require.NoError(t, committer.Commit())
|
||||
}
|
||||
@@ -50,30 +50,46 @@ func TestActionsRunnerModify(t *testing.T) {
|
||||
|
||||
doUpdate := func(t *testing.T, sess *TestSession, baseURL string, id int64, description string, expectedStatus int) {
|
||||
req := NewRequestWithValues(t, "POST", fmt.Sprintf("%s/%d", baseURL, id), map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, sess),
|
||||
"description": description,
|
||||
})
|
||||
sess.MakeRequest(t, req, expectedStatus)
|
||||
}
|
||||
|
||||
doDelete := func(t *testing.T, sess *TestSession, baseURL string, id int64, expectedStatus int) {
|
||||
req := NewRequestWithValues(t, "POST", fmt.Sprintf("%s/%d/delete", baseURL, id), map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, sess),
|
||||
})
|
||||
req := NewRequest(t, "POST", fmt.Sprintf("%s/%d/delete", baseURL, id))
|
||||
sess.MakeRequest(t, req, expectedStatus)
|
||||
}
|
||||
|
||||
doDisable := func(t *testing.T, sess *TestSession, baseURL string, id int64, expectedStatus int) {
|
||||
req := NewRequest(t, "POST", fmt.Sprintf("%s/%d/update-runner?disabled=true", baseURL, id))
|
||||
sess.MakeRequest(t, req, expectedStatus)
|
||||
}
|
||||
|
||||
doEnable := func(t *testing.T, sess *TestSession, baseURL string, id int64, expectedStatus int) {
|
||||
req := NewRequest(t, "POST", fmt.Sprintf("%s/%d/update-runner?disabled=false", baseURL, id))
|
||||
sess.MakeRequest(t, req, expectedStatus)
|
||||
}
|
||||
|
||||
assertDenied := func(t *testing.T, sess *TestSession, baseURL string, id int64) {
|
||||
doUpdate(t, sess, baseURL, id, "ChangedDescription", http.StatusNotFound)
|
||||
doDisable(t, sess, baseURL, id, http.StatusNotFound)
|
||||
doEnable(t, sess, baseURL, id, http.StatusNotFound)
|
||||
doDelete(t, sess, baseURL, id, http.StatusNotFound)
|
||||
v := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: id})
|
||||
assert.Empty(t, v.Description)
|
||||
assert.False(t, v.IsDisabled)
|
||||
}
|
||||
|
||||
assertSuccess := func(t *testing.T, sess *TestSession, baseURL string, id int64) {
|
||||
doUpdate(t, sess, baseURL, id, "ChangedDescription", http.StatusSeeOther)
|
||||
v := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: id})
|
||||
assert.Equal(t, "ChangedDescription", v.Description)
|
||||
doDisable(t, sess, baseURL, id, http.StatusOK)
|
||||
v = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: id})
|
||||
assert.True(t, v.IsDisabled)
|
||||
doEnable(t, sess, baseURL, id, http.StatusOK)
|
||||
v = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: id})
|
||||
assert.False(t, v.IsDisabled)
|
||||
doDelete(t, sess, baseURL, id, http.StatusOK)
|
||||
unittest.AssertNotExistsBean(t, &actions_model.ActionRunner{ID: id})
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"code.gitea.io/actions-proto-go/runner/v1/runnerv1connect"
|
||||
"connectrpc.com/connect"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
@@ -83,7 +84,7 @@ func (r *mockRunner) doRegister(t *testing.T, name, token string, labels []strin
|
||||
func (r *mockRunner) registerAsRepoRunner(t *testing.T, ownerName, repoName, runnerName string, labels []string, ephemeral bool) {
|
||||
session := loginUser(t, ownerName)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/actions/runners/registration-token", ownerName, repoName)).AddTokenAuth(token)
|
||||
req := NewRequest(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/actions/runners/registration-token", ownerName, repoName)).AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
var registrationToken struct {
|
||||
Token string `json:"token"`
|
||||
@@ -94,13 +95,13 @@ func (r *mockRunner) registerAsRepoRunner(t *testing.T, ownerName, repoName, run
|
||||
|
||||
func (r *mockRunner) fetchTask(t *testing.T, timeout ...time.Duration) *runnerv1.Task {
|
||||
task := r.tryFetchTask(t, timeout...)
|
||||
assert.NotNil(t, task, "failed to fetch a task")
|
||||
require.NotNil(t, task, "failed to fetch a task")
|
||||
return task
|
||||
}
|
||||
|
||||
func (r *mockRunner) fetchNoTask(t *testing.T, timeout ...time.Duration) {
|
||||
task := r.tryFetchTask(t, timeout...)
|
||||
assert.Nil(t, task, "a task is fetched")
|
||||
require.Nil(t, task, "a task is fetched")
|
||||
}
|
||||
|
||||
const defaultFetchTaskTimeout = 1 * time.Second
|
||||
@@ -113,12 +114,8 @@ func (r *mockRunner) tryFetchTask(t *testing.T, timeout ...time.Duration) *runne
|
||||
ddl := time.Now().Add(fetchTimeout)
|
||||
var task *runnerv1.Task
|
||||
for time.Now().Before(ddl) {
|
||||
resp, err := r.client.runnerServiceClient.FetchTask(t.Context(), connect.NewRequest(&runnerv1.FetchTaskRequest{
|
||||
TasksVersion: 0,
|
||||
}))
|
||||
assert.NoError(t, err)
|
||||
if resp.Msg.Task != nil {
|
||||
task = resp.Msg.Task
|
||||
task, _ = r.fetchTaskOnce(t, 0)
|
||||
if task != nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
@@ -127,6 +124,17 @@ func (r *mockRunner) tryFetchTask(t *testing.T, timeout ...time.Duration) *runne
|
||||
return task
|
||||
}
|
||||
|
||||
// fetchTaskOnce performs a single FetchTask request with the given TasksVersion
|
||||
// and returns the task (if any) and the TasksVersion from the response.
|
||||
// Used to verify the production path where the runner sends the current version.
|
||||
func (r *mockRunner) fetchTaskOnce(t *testing.T, tasksVersion int64) (*runnerv1.Task, int64) {
|
||||
resp, err := r.client.runnerServiceClient.FetchTask(t.Context(), connect.NewRequest(&runnerv1.FetchTaskRequest{
|
||||
TasksVersion: tasksVersion,
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
return resp.Msg.Task, resp.Msg.TasksVersion
|
||||
}
|
||||
|
||||
type mockTaskOutcome struct {
|
||||
result runnerv1.Result
|
||||
outputs map[string]string
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/migration"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
mirror_service "code.gitea.io/gitea/services/mirror"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
files_service "code.gitea.io/gitea/services/repository/files"
|
||||
@@ -89,7 +88,9 @@ jobs:
|
||||
assert.NoError(t, err)
|
||||
|
||||
// merge pull request
|
||||
testPullMerge(t, testContext.Session, repo.OwnerName, repo.Name, strconv.FormatInt(apiPull.Index, 10), mergeStyle, false)
|
||||
testPullMerge(t, testContext.Session, repo.OwnerName, repo.Name, strconv.FormatInt(apiPull.Index, 10), MergeOptions{
|
||||
Style: mergeStyle,
|
||||
})
|
||||
|
||||
pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID})
|
||||
return pull.MergedCommitID, "@every 2m"
|
||||
@@ -101,8 +102,8 @@ jobs:
|
||||
doTestScheduleUpdate(t, func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string) {
|
||||
// enable manual-merge
|
||||
doAPIEditRepository(testContext, &api.EditRepoOption{
|
||||
HasPullRequests: util.ToPointer(true),
|
||||
AllowManualMerge: util.ToPointer(true),
|
||||
HasPullRequests: new(true),
|
||||
AllowManualMerge: new(true),
|
||||
})(t)
|
||||
|
||||
// update workflow file
|
||||
@@ -168,7 +169,7 @@ func testScheduleUpdateMirrorSync(t *testing.T) {
|
||||
// enable actions unit for mirror repo
|
||||
assert.False(t, mirrorRepo.UnitEnabled(t.Context(), unit_model.TypeActions))
|
||||
doAPIEditRepository(mirrorContext, &api.EditRepoOption{
|
||||
HasActions: util.ToPointer(true),
|
||||
HasActions: new(true),
|
||||
})(t)
|
||||
actionSchedule := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionSchedule{RepoID: mirrorRepo.ID})
|
||||
scheduleSpec := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionScheduleSpec{RepoID: mirrorRepo.ID, ScheduleID: actionSchedule.ID})
|
||||
@@ -199,11 +200,11 @@ func testScheduleUpdateMirrorSync(t *testing.T) {
|
||||
func testScheduleUpdateArchiveAndUnarchive(t *testing.T) {
|
||||
doTestScheduleUpdate(t, func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string) {
|
||||
doAPIEditRepository(testContext, &api.EditRepoOption{
|
||||
Archived: util.ToPointer(true),
|
||||
Archived: new(true),
|
||||
})(t)
|
||||
assert.Zero(t, unittest.GetCount(t, &actions_model.ActionSchedule{RepoID: repo.ID}))
|
||||
doAPIEditRepository(testContext, &api.EditRepoOption{
|
||||
Archived: util.ToPointer(false),
|
||||
Archived: new(false),
|
||||
})(t)
|
||||
branch, err := git_model.GetBranch(t.Context(), repo.ID, repo.DefaultBranch)
|
||||
assert.NoError(t, err)
|
||||
@@ -214,11 +215,11 @@ func testScheduleUpdateArchiveAndUnarchive(t *testing.T) {
|
||||
func testScheduleUpdateDisableAndEnableActionsUnit(t *testing.T) {
|
||||
doTestScheduleUpdate(t, func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string) {
|
||||
doAPIEditRepository(testContext, &api.EditRepoOption{
|
||||
HasActions: util.ToPointer(false),
|
||||
HasActions: new(false),
|
||||
})(t)
|
||||
assert.Zero(t, unittest.GetCount(t, &actions_model.ActionSchedule{RepoID: repo.ID}))
|
||||
doAPIEditRepository(testContext, &api.EditRepoOption{
|
||||
HasActions: util.ToPointer(true),
|
||||
HasActions: new(true),
|
||||
})(t)
|
||||
branch, err := git_model.GetBranch(t.Context(), repo.ID, repo.DefaultBranch)
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestActionsCollaborativeOwner(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
// user2 is the owner of the private "reusable_workflow" repo
|
||||
user2Session := loginUser(t, "user2")
|
||||
user2Token := getTokenForLoggedInUser(t, user2Session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
apiReusableWorkflowRepo := createActionsTestRepo(t, user2Token, "reusable_workflow", true)
|
||||
reusableWorkflowRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiReusableWorkflowRepo.ID})
|
||||
|
||||
// user4 is the owner of the private caller repo
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
user4Session := loginUser(t, user4.Name)
|
||||
user4Token := getTokenForLoggedInUser(t, user4Session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
apiCallerRepo := createActionsTestRepo(t, user4Token, "caller_workflow", true)
|
||||
callerRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiCallerRepo.ID})
|
||||
|
||||
// create a mock runner for caller
|
||||
runner := newMockRunner()
|
||||
runner.registerAsRepoRunner(t, callerRepo.OwnerName, callerRepo.Name, "mock-runner", []string{"ubuntu-latest"}, false)
|
||||
|
||||
// init the workflow for caller
|
||||
wfTreePath := ".gitea/workflows/test_collaborative_owner.yml"
|
||||
wfFileContent := `name: Test Collaborative Owner
|
||||
on: push
|
||||
jobs:
|
||||
job1:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo 'test collaborative owner'
|
||||
`
|
||||
opts := getWorkflowCreateFileOptions(user4, callerRepo.DefaultBranch, "create "+wfTreePath, wfFileContent)
|
||||
createWorkflowFile(t, user4Token, callerRepo.OwnerName, callerRepo.Name, wfTreePath, opts)
|
||||
|
||||
// fetch the task and get its token
|
||||
task := runner.fetchTask(t)
|
||||
taskToken := task.Secrets["GITEA_TOKEN"]
|
||||
assert.NotEmpty(t, taskToken)
|
||||
|
||||
// prepare for clone
|
||||
dstPath := t.TempDir()
|
||||
u.Path = fmt.Sprintf("%s/%s.git", "user2", "reusable_workflow")
|
||||
u.User = url.UserPassword("gitea-actions", taskToken)
|
||||
|
||||
// the git clone will fail
|
||||
doGitCloneFail(u)(t)
|
||||
|
||||
// add user10 to the list of collaborative owners
|
||||
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/collaborative_owner/add", reusableWorkflowRepo.OwnerName, reusableWorkflowRepo.Name), map[string]string{
|
||||
"collaborative_owner": user4.Name,
|
||||
})
|
||||
user2Session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// the git clone will be successful
|
||||
doGitClone(dstPath, u)(t)
|
||||
|
||||
// remove user10 from the list of collaborative owners
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/collaborative_owner/delete?id=%d", reusableWorkflowRepo.OwnerName, reusableWorkflowRepo.Name, user4.ID))
|
||||
user2Session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// the git clone will fail
|
||||
doGitCloneFail(u)(t)
|
||||
})
|
||||
}
|
||||
@@ -23,14 +23,12 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
actions_module "code.gitea.io/gitea/modules/actions"
|
||||
"code.gitea.io/gitea/modules/commitstatus"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
pull_service "code.gitea.io/gitea/services/pull"
|
||||
@@ -40,6 +38,7 @@ import (
|
||||
files_service "code.gitea.io/gitea/services/repository/files"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPullRequestTargetEvent(t *testing.T) {
|
||||
@@ -413,7 +412,7 @@ jobs:
|
||||
assert.NoError(t, err)
|
||||
|
||||
// create a branch
|
||||
err = repo_service.CreateNewBranchFromCommit(t.Context(), user2, repo, gitRepo, branch.CommitID, "test-create-branch")
|
||||
err = repo_service.CreateNewBranchFromCommit(t.Context(), user2, repo, branch.CommitID, "test-create-branch")
|
||||
assert.NoError(t, err)
|
||||
run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{
|
||||
Title: "add workflow",
|
||||
@@ -439,7 +438,7 @@ jobs:
|
||||
assert.NotNil(t, run)
|
||||
|
||||
// delete the branch
|
||||
err = repo_service.DeleteBranch(t.Context(), user2, repo, gitRepo, "test-create-branch", nil)
|
||||
err = repo_service.DeleteBranch(t.Context(), user2, repo, gitRepo, "test-create-branch")
|
||||
assert.NoError(t, err)
|
||||
run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{
|
||||
Title: "add workflow",
|
||||
@@ -531,9 +530,7 @@ jobs:
|
||||
|
||||
// create a new branch
|
||||
testBranch := "test-branch"
|
||||
gitRepo, err := git.OpenRepository(t.Context(), ".")
|
||||
assert.NoError(t, err)
|
||||
err = repo_service.CreateNewBranch(t.Context(), user2, repo, gitRepo, "main", testBranch)
|
||||
err = repo_service.CreateNewBranch(t.Context(), user2, repo, "main", testBranch)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// create Pull
|
||||
@@ -695,6 +692,144 @@ func insertFakeStatus(t *testing.T, repo *repo_model.Repository, sha, targetURL,
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestPullRequestReviewCommitStatusEvent(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // reviewer
|
||||
|
||||
// create a repo
|
||||
repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{
|
||||
Name: "repo-pull-request-review",
|
||||
Description: "test pull-request-review commit status",
|
||||
AutoInit: true,
|
||||
Gitignores: "Go",
|
||||
License: "MIT",
|
||||
Readme: "Default",
|
||||
DefaultBranch: "main",
|
||||
IsPrivate: false,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, repo)
|
||||
|
||||
// add user4 as collaborator so they can review
|
||||
ctx := NewAPITestContext(t, repo.OwnerName, repo.Name, auth_model.AccessTokenScopeWriteRepository)
|
||||
t.Run("AddUser4AsCollaboratorWithWriteAccess", doAPIAddCollaborator(ctx, "user4", perm.AccessModeWrite))
|
||||
|
||||
// add workflow file that triggers on pull_request_review
|
||||
addWorkflow, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{
|
||||
Files: []*files_service.ChangeRepoFile{
|
||||
{
|
||||
Operation: "create",
|
||||
TreePath: ".gitea/workflows/pr-review.yml",
|
||||
ContentReader: strings.NewReader(`name: test
|
||||
on:
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo helloworld
|
||||
`),
|
||||
},
|
||||
},
|
||||
Message: "add workflow",
|
||||
OldBranch: "main",
|
||||
NewBranch: "main",
|
||||
Author: &files_service.IdentityOptions{
|
||||
GitUserName: user2.Name,
|
||||
GitUserEmail: user2.Email,
|
||||
},
|
||||
Committer: &files_service.IdentityOptions{
|
||||
GitUserName: user2.Name,
|
||||
GitUserEmail: user2.Email,
|
||||
},
|
||||
Dates: &files_service.CommitDateOptions{
|
||||
Author: time.Now(),
|
||||
Committer: time.Now(),
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, addWorkflow)
|
||||
|
||||
// create a branch and a PR
|
||||
testBranch := "test-review-branch"
|
||||
err = repo_service.CreateNewBranch(t.Context(), user2, repo, "main", testBranch)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// add a file on the test branch so the PR has changes
|
||||
addFileResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{
|
||||
Files: []*files_service.ChangeRepoFile{
|
||||
{
|
||||
Operation: "create",
|
||||
TreePath: "test.txt",
|
||||
ContentReader: strings.NewReader("test content"),
|
||||
},
|
||||
},
|
||||
Message: "add test file",
|
||||
OldBranch: testBranch,
|
||||
NewBranch: testBranch,
|
||||
Author: &files_service.IdentityOptions{
|
||||
GitUserName: user2.Name,
|
||||
GitUserEmail: user2.Email,
|
||||
},
|
||||
Committer: &files_service.IdentityOptions{
|
||||
GitUserName: user2.Name,
|
||||
GitUserEmail: user2.Email,
|
||||
},
|
||||
Dates: &files_service.CommitDateOptions{
|
||||
Author: time.Now(),
|
||||
Committer: time.Now(),
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, addFileResp)
|
||||
sha := addFileResp.Commit.SHA
|
||||
|
||||
pullIssue := &issues_model.Issue{
|
||||
RepoID: repo.ID,
|
||||
Title: "A test PR for review",
|
||||
PosterID: user2.ID,
|
||||
Poster: user2,
|
||||
IsPull: true,
|
||||
}
|
||||
pullRequest := &issues_model.PullRequest{
|
||||
HeadRepoID: repo.ID,
|
||||
BaseRepoID: repo.ID,
|
||||
HeadBranch: testBranch,
|
||||
BaseBranch: "main",
|
||||
HeadRepo: repo,
|
||||
BaseRepo: repo,
|
||||
Type: issues_model.PullRequestGitea,
|
||||
}
|
||||
prOpts := &pull_service.NewPullRequestOptions{Repo: repo, Issue: pullIssue, PullRequest: pullRequest}
|
||||
err = pull_service.NewPullRequest(t.Context(), prOpts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// submit an approval review as user4
|
||||
gitRepo, err := gitrepo.OpenRepository(t.Context(), repo)
|
||||
assert.NoError(t, err)
|
||||
defer gitRepo.Close()
|
||||
|
||||
_, _, err = pull_service.SubmitReview(t.Context(), user4, gitRepo, pullIssue, issues_model.ReviewTypeApprove, "lgtm", sha, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// verify that a commit status was created for the review event
|
||||
assert.Eventually(t, func() bool {
|
||||
latestCommitStatuses, err := git_model.GetLatestCommitStatus(t.Context(), repo.ID, sha, db.ListOptionsAll)
|
||||
assert.NoError(t, err)
|
||||
if len(latestCommitStatuses) == 0 {
|
||||
return false
|
||||
}
|
||||
if latestCommitStatuses[0].State == commitstatus.CommitStatusPending {
|
||||
insertFakeStatus(t, repo, sha, latestCommitStatuses[0].TargetURL, latestCommitStatuses[0].Context)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}, 1*time.Second, 100*time.Millisecond)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWorkflowDispatchPublicApi(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
@@ -772,6 +907,27 @@ jobs:
|
||||
CommitSHA: branch.CommitID,
|
||||
})
|
||||
assert.NotNil(t, run)
|
||||
|
||||
// Now trigger with rundetails
|
||||
values.Set("return_run_details", "true")
|
||||
|
||||
req = NewRequestWithURLValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), values).
|
||||
AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
runDetails := &api.RunDetails{}
|
||||
require.NoError(t, json.NewDecoder(resp.Body).Decode(runDetails))
|
||||
assert.NotEqual(t, 0, runDetails.WorkflowRunID)
|
||||
|
||||
run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{
|
||||
ID: runDetails.WorkflowRunID,
|
||||
Title: "add workflow",
|
||||
RepoID: repo.ID,
|
||||
Event: "workflow_dispatch",
|
||||
Ref: "refs/heads/main",
|
||||
WorkflowID: "dispatch.yml",
|
||||
CommitSHA: branch.CommitID,
|
||||
})
|
||||
assert.NotNil(t, run)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1409,7 +1565,7 @@ jobs:
|
||||
// user4 forks the repo
|
||||
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/forks", baseRepo.OwnerName, baseRepo.Name),
|
||||
&api.CreateForkOption{
|
||||
Name: util.ToPointer("close-pull-request-with-path-fork"),
|
||||
Name: new("close-pull-request-with-path-fork"),
|
||||
}).AddTokenAuth(user4Token)
|
||||
resp := MakeRequest(t, req, http.StatusAccepted)
|
||||
var apiForkRepo api.Repository
|
||||
@@ -1508,14 +1664,11 @@ jobs:
|
||||
assert.NotEmpty(t, addWorkflowToBaseResp)
|
||||
|
||||
// Get the commit ID of the default branch
|
||||
gitRepo, err := gitrepo.OpenRepository(t.Context(), repo)
|
||||
assert.NoError(t, err)
|
||||
defer gitRepo.Close()
|
||||
branch, err := git_model.GetBranch(t.Context(), repo.ID, repo.DefaultBranch)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// create a branch
|
||||
err = repo_service.CreateNewBranchFromCommit(t.Context(), user2, repo, gitRepo, branch.CommitID, "test-action-run-name-with-variables")
|
||||
err = repo_service.CreateNewBranchFromCommit(t.Context(), user2, repo, branch.CommitID, "test-action-run-name-with-variables")
|
||||
assert.NoError(t, err)
|
||||
run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{
|
||||
Title: user2.LoginName + " is running this workflow",
|
||||
@@ -1585,14 +1738,11 @@ jobs:
|
||||
assert.NotEmpty(t, addWorkflowToBaseResp)
|
||||
|
||||
// Get the commit ID of the default branch
|
||||
gitRepo, err := gitrepo.OpenRepository(t.Context(), repo)
|
||||
assert.NoError(t, err)
|
||||
defer gitRepo.Close()
|
||||
branch, err := git_model.GetBranch(t.Context(), repo.ID, repo.DefaultBranch)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// create a branch
|
||||
err = repo_service.CreateNewBranchFromCommit(t.Context(), user2, repo, gitRepo, branch.CommitID, "test-action-run-name")
|
||||
err = repo_service.CreateNewBranchFromCommit(t.Context(), user2, repo, branch.CommitID, "test-action-run-name")
|
||||
assert.NoError(t, err)
|
||||
run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{
|
||||
Title: "run name without variables",
|
||||
@@ -1623,7 +1773,7 @@ func TestPullRequestWithPathsRebase(t *testing.T) {
|
||||
testCreateFile(t, session, "user2", repoName, repo.DefaultBranch, "", "dir1/dir1.txt", "1")
|
||||
testCreateFile(t, session, "user2", repoName, repo.DefaultBranch, "", "dir2/dir2.txt", "2")
|
||||
wfFileContent := `name: ci
|
||||
on:
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'dir1/**'
|
||||
@@ -1648,12 +1798,10 @@ jobs:
|
||||
apiPull, err := doAPICreatePullRequest(apiCtx, "user2", repoName, repo.DefaultBranch, "update-dir2")(t)
|
||||
runner.fetchNoTask(t)
|
||||
assert.NoError(t, err)
|
||||
testEditFile(t, session, "user2", repoName, repo.DefaultBranch, "dir1/dir1.txt", "11") // change the file in "dir1"
|
||||
req := NewRequestWithValues(t, "POST",
|
||||
fmt.Sprintf("/%s/%s/pulls/%d/update?style=rebase", "user2", repoName, apiPull.Index), // update by rebase
|
||||
map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
})
|
||||
// change the file in "dir1"
|
||||
testEditFile(t, session, "user2", repoName, repo.DefaultBranch, "dir1/dir1.txt", "11")
|
||||
// update by rebase
|
||||
req := NewRequest(t, "POST", fmt.Sprintf("/%s/%s/pulls/%d/update?style=rebase", "user2", repoName, apiPull.Index))
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
runner.fetchNoTask(t)
|
||||
})
|
||||
|
||||
@@ -50,17 +50,14 @@ func TestActionsVariables(t *testing.T) {
|
||||
|
||||
doUpdate := func(t *testing.T, sess *TestSession, baseURL string, id int64, data string, expectedStatus int) {
|
||||
req := NewRequestWithValues(t, "POST", fmt.Sprintf("%s/%d/edit", baseURL, id), map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, sess),
|
||||
"name": "VAR",
|
||||
"data": data,
|
||||
"name": "VAR",
|
||||
"data": data,
|
||||
})
|
||||
sess.MakeRequest(t, req, expectedStatus)
|
||||
}
|
||||
|
||||
doDelete := func(t *testing.T, sess *TestSession, baseURL string, id int64, expectedStatus int) {
|
||||
req := NewRequestWithValues(t, "POST", fmt.Sprintf("%s/%d/delete", baseURL, id), map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, sess),
|
||||
})
|
||||
req := NewRequest(t, "POST", fmt.Sprintf("%s/%d/delete", baseURL, id))
|
||||
sess.MakeRequest(t, req, expectedStatus)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,10 +7,14 @@ import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/system"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/setting/config"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAdminConfig(t *testing.T) {
|
||||
@@ -20,4 +24,46 @@ func TestAdminConfig(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/-/admin/config")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
assert.True(t, test.IsNormalPageCompleted(resp.Body.String()))
|
||||
|
||||
t.Run("OpenEditorWithApps", func(t *testing.T) {
|
||||
cfg := setting.Config().Repository.OpenWithEditorApps
|
||||
editorApps := cfg.Value(t.Context())
|
||||
assert.Len(t, editorApps, 3)
|
||||
assert.False(t, cfg.HasValue(t.Context()))
|
||||
|
||||
require.NoError(t, system.SetSettings(t.Context(), map[string]string{cfg.DynKey(): "[]"}))
|
||||
config.GetDynGetter().InvalidateCache()
|
||||
|
||||
editorApps = cfg.Value(t.Context())
|
||||
assert.Len(t, editorApps, 3)
|
||||
assert.False(t, cfg.HasValue(t.Context()))
|
||||
|
||||
require.NoError(t, system.SetSettings(t.Context(), map[string]string{cfg.DynKey(): "[{}]"}))
|
||||
config.GetDynGetter().InvalidateCache()
|
||||
|
||||
editorApps = cfg.Value(t.Context())
|
||||
assert.Len(t, editorApps, 1)
|
||||
assert.True(t, cfg.HasValue(t.Context()))
|
||||
})
|
||||
|
||||
t.Run("InstanceWebBanner", func(t *testing.T) {
|
||||
banner, rev1, has := setting.Config().Instance.WebBanner.ValueRevision(t.Context())
|
||||
assert.False(t, has)
|
||||
assert.Equal(t, setting.WebBannerType{}, banner)
|
||||
|
||||
req = NewRequestWithValues(t, "POST", "/-/admin/config", map[string]string{
|
||||
"key": "instance.web_banner",
|
||||
"value": `{"DisplayEnabled":true,"ContentMessage":"test-msg","StartTimeUnix":123,"EndTimeUnix":456}`,
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
banner, rev2, has := setting.Config().Instance.WebBanner.ValueRevision(t.Context())
|
||||
assert.NotEqual(t, rev1, rev2)
|
||||
assert.True(t, has)
|
||||
assert.Equal(t, setting.WebBannerType{
|
||||
DisplayEnabled: true,
|
||||
ContentMessage: "test-msg",
|
||||
StartTimeUnix: 123,
|
||||
EndTimeUnix: 456,
|
||||
}, banner)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -43,18 +43,16 @@ func TestAdminViewUser(t *testing.T) {
|
||||
func TestAdminEditUser(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
testSuccessfullEdit(t, user_model.User{ID: 2, Name: "newusername", LoginName: "otherlogin", Email: "new@e-mail.gitea"})
|
||||
testSuccessfulEdit(t, user_model.User{ID: 2, Name: "newusername", LoginName: "otherlogin", Email: "new@e-mail.gitea"})
|
||||
}
|
||||
|
||||
func testSuccessfullEdit(t *testing.T, formData user_model.User) {
|
||||
func testSuccessfulEdit(t *testing.T, formData user_model.User) {
|
||||
makeRequest(t, formData, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func makeRequest(t *testing.T, formData user_model.User, headerCode int) {
|
||||
session := loginUser(t, "user1")
|
||||
csrf := GetUserCSRFToken(t, session)
|
||||
req := NewRequestWithValues(t, "POST", "/-/admin/users/"+strconv.Itoa(int(formData.ID))+"/edit", map[string]string{
|
||||
"_csrf": csrf,
|
||||
"user_name": formData.Name,
|
||||
"login_name": formData.LoginName,
|
||||
"login_type": "0-0",
|
||||
@@ -96,10 +94,7 @@ func TestAdminDeleteUser(t *testing.T) {
|
||||
query = "?purge=true"
|
||||
}
|
||||
|
||||
csrf := GetUserCSRFToken(t, session)
|
||||
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/-/admin/users/%d/delete%s", entry.userID, query), map[string]string{
|
||||
"_csrf": csrf,
|
||||
})
|
||||
req := NewRequest(t, "POST", fmt.Sprintf("/-/admin/users/%d/delete%s", entry.userID, query))
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
assertUserDeleted(t, entry.userID)
|
||||
|
||||
@@ -6,26 +6,33 @@ package integration
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/routers/api/actions"
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
@@ -45,45 +52,168 @@ func TestActionsArtifactV4UploadSingleFile(t *testing.T) {
|
||||
token, err := actions_service.CreateAuthorizationToken(48, 792, 193)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// acquire artifact upload url
|
||||
req := NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/CreateArtifact", toProtoJSON(&actions.CreateArtifactRequest{
|
||||
Version: 4,
|
||||
Name: "artifact",
|
||||
WorkflowRunBackendId: "792",
|
||||
WorkflowJobRunBackendId: "193",
|
||||
})).AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
var uploadResp actions.CreateArtifactResponse
|
||||
protojson.Unmarshal(resp.Body.Bytes(), &uploadResp)
|
||||
assert.True(t, uploadResp.Ok)
|
||||
assert.Contains(t, uploadResp.SignedUploadUrl, "/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact")
|
||||
table := []struct {
|
||||
name string
|
||||
version int32
|
||||
contentType string
|
||||
blockID bool
|
||||
noLength bool
|
||||
append int
|
||||
path string
|
||||
}{
|
||||
{
|
||||
name: "artifact",
|
||||
version: 4,
|
||||
path: "artifact.zip",
|
||||
},
|
||||
{
|
||||
name: "artifact2",
|
||||
version: 4,
|
||||
blockID: true,
|
||||
},
|
||||
{
|
||||
name: "artifact3",
|
||||
version: 4,
|
||||
noLength: true,
|
||||
},
|
||||
{
|
||||
name: "artifact4",
|
||||
version: 4,
|
||||
blockID: true,
|
||||
noLength: true,
|
||||
},
|
||||
{
|
||||
name: "artifact5",
|
||||
version: 7,
|
||||
blockID: true,
|
||||
},
|
||||
{
|
||||
name: "artifact6",
|
||||
version: 7,
|
||||
append: 2,
|
||||
noLength: true,
|
||||
},
|
||||
{
|
||||
name: "artifact7",
|
||||
version: 7,
|
||||
append: 3,
|
||||
blockID: true,
|
||||
noLength: true,
|
||||
},
|
||||
{
|
||||
name: "artifact8",
|
||||
version: 7,
|
||||
append: 4,
|
||||
blockID: true,
|
||||
},
|
||||
{
|
||||
name: "artifact9.json",
|
||||
version: 7,
|
||||
contentType: "application/json",
|
||||
},
|
||||
{
|
||||
name: "artifact10",
|
||||
version: 7,
|
||||
contentType: "application/zip",
|
||||
path: "artifact10.zip",
|
||||
},
|
||||
{
|
||||
name: "artifact11.zip",
|
||||
version: 7,
|
||||
contentType: "application/zip",
|
||||
path: "artifact11.zip",
|
||||
},
|
||||
}
|
||||
|
||||
// get upload url
|
||||
idx := strings.Index(uploadResp.SignedUploadUrl, "/twirp/")
|
||||
url := uploadResp.SignedUploadUrl[idx:] + "&comp=block"
|
||||
for _, entry := range table {
|
||||
t.Run(entry.name, func(t *testing.T) {
|
||||
// acquire artifact upload url
|
||||
req := NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/CreateArtifact", toProtoJSON(&actions.CreateArtifactRequest{
|
||||
Version: entry.version,
|
||||
Name: entry.name,
|
||||
WorkflowRunBackendId: "792",
|
||||
WorkflowJobRunBackendId: "193",
|
||||
MimeType: util.Iif(entry.contentType != "", wrapperspb.String(entry.contentType), nil),
|
||||
})).AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
var uploadResp actions.CreateArtifactResponse
|
||||
protojson.Unmarshal(resp.Body.Bytes(), &uploadResp)
|
||||
assert.True(t, uploadResp.Ok)
|
||||
assert.Contains(t, uploadResp.SignedUploadUrl, "/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact")
|
||||
|
||||
// upload artifact chunk
|
||||
body := strings.Repeat("A", 1024)
|
||||
req = NewRequestWithBody(t, "PUT", url, strings.NewReader(body))
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
h := sha256.New()
|
||||
|
||||
t.Logf("Create artifact confirm")
|
||||
blocks := make([]string, 0, util.Iif(entry.blockID, entry.append+1, 0))
|
||||
|
||||
sha := sha256.Sum256([]byte(body))
|
||||
// get upload url
|
||||
for i := range entry.append + 1 {
|
||||
url := uploadResp.SignedUploadUrl
|
||||
// See https://learn.microsoft.com/en-us/rest/api/storageservices/append-block
|
||||
// See https://learn.microsoft.com/en-us/rest/api/storageservices/put-block
|
||||
if entry.blockID {
|
||||
blockID := base64.RawURLEncoding.EncodeToString(fmt.Append([]byte("SOME_BIG_BLOCK_ID_"), i))
|
||||
blocks = append(blocks, blockID)
|
||||
url += "&comp=block&blockid=" + blockID
|
||||
} else {
|
||||
url += "&comp=appendBlock"
|
||||
}
|
||||
|
||||
// confirm artifact upload
|
||||
req = NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/FinalizeArtifact", toProtoJSON(&actions.FinalizeArtifactRequest{
|
||||
Name: "artifact",
|
||||
Size: 1024,
|
||||
Hash: wrapperspb.String("sha256:" + hex.EncodeToString(sha[:])),
|
||||
WorkflowRunBackendId: "792",
|
||||
WorkflowJobRunBackendId: "193",
|
||||
})).
|
||||
AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
var finalizeResp actions.FinalizeArtifactResponse
|
||||
protojson.Unmarshal(resp.Body.Bytes(), &finalizeResp)
|
||||
assert.True(t, finalizeResp.Ok)
|
||||
// upload artifact chunk
|
||||
body := strings.Repeat("A", 1024)
|
||||
_, _ = h.Write([]byte(body))
|
||||
var bodyReader io.Reader = strings.NewReader(body)
|
||||
if entry.noLength {
|
||||
bodyReader = io.MultiReader(bodyReader)
|
||||
}
|
||||
req = NewRequestWithBody(t, "PUT", url, bodyReader)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
}
|
||||
|
||||
if entry.blockID && entry.append > 0 {
|
||||
// https://learn.microsoft.com/en-us/rest/api/storageservices/put-block-list
|
||||
blockListURL := uploadResp.SignedUploadUrl + "&comp=blocklist"
|
||||
// upload artifact blockList
|
||||
blockList := &actions.BlockList{
|
||||
Latest: blocks,
|
||||
}
|
||||
rawBlockList, err := xml.Marshal(blockList)
|
||||
assert.NoError(t, err)
|
||||
req = NewRequestWithBody(t, "PUT", blockListURL, bytes.NewReader(rawBlockList))
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
}
|
||||
|
||||
sha := h.Sum(nil)
|
||||
|
||||
t.Logf("Create artifact confirm")
|
||||
|
||||
// confirm artifact upload
|
||||
req = NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/FinalizeArtifact", toProtoJSON(&actions.FinalizeArtifactRequest{
|
||||
Name: entry.name,
|
||||
Size: int64(entry.append+1) * 1024,
|
||||
Hash: wrapperspb.String("sha256:" + hex.EncodeToString(sha)),
|
||||
WorkflowRunBackendId: "792",
|
||||
WorkflowJobRunBackendId: "193",
|
||||
})).
|
||||
AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
var finalizeResp actions.FinalizeArtifactResponse
|
||||
protojson.Unmarshal(resp.Body.Bytes(), &finalizeResp)
|
||||
assert.True(t, finalizeResp.Ok)
|
||||
|
||||
artifact := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionArtifact{ID: finalizeResp.ArtifactId})
|
||||
if entry.contentType != "" {
|
||||
assert.Equal(t, entry.contentType, artifact.ContentEncodingOrType)
|
||||
} else {
|
||||
assert.Equal(t, "application/zip", artifact.ContentEncodingOrType)
|
||||
}
|
||||
if entry.path != "" {
|
||||
assert.Equal(t, entry.path, artifact.ArtifactPath)
|
||||
}
|
||||
assert.Equal(t, actions_model.ArtifactStatusUploadConfirmed, artifact.Status)
|
||||
assert.Equal(t, int64(entry.append+1)*1024, artifact.FileSize)
|
||||
assert.Equal(t, int64(entry.append+1)*1024, artifact.FileCompressedSize)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestActionsArtifactV4UploadSingleFileWrongChecksum(t *testing.T) {
|
||||
@@ -106,8 +236,7 @@ func TestActionsArtifactV4UploadSingleFileWrongChecksum(t *testing.T) {
|
||||
assert.Contains(t, uploadResp.SignedUploadUrl, "/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact")
|
||||
|
||||
// get upload url
|
||||
idx := strings.Index(uploadResp.SignedUploadUrl, "/twirp/")
|
||||
url := uploadResp.SignedUploadUrl[idx:] + "&comp=block"
|
||||
url := uploadResp.SignedUploadUrl + "&comp=block"
|
||||
|
||||
// upload artifact chunk
|
||||
body := strings.Repeat("B", 1024)
|
||||
@@ -151,8 +280,7 @@ func TestActionsArtifactV4UploadSingleFileWithRetentionDays(t *testing.T) {
|
||||
assert.Contains(t, uploadResp.SignedUploadUrl, "/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact")
|
||||
|
||||
// get upload url
|
||||
idx := strings.Index(uploadResp.SignedUploadUrl, "/twirp/")
|
||||
url := uploadResp.SignedUploadUrl[idx:] + "&comp=block"
|
||||
url := uploadResp.SignedUploadUrl + "&comp=block"
|
||||
|
||||
// upload artifact chunk
|
||||
body := strings.Repeat("A", 1024)
|
||||
@@ -198,9 +326,8 @@ func TestActionsArtifactV4UploadSingleFileWithPotentialHarmfulBlockID(t *testing
|
||||
assert.Contains(t, uploadResp.SignedUploadUrl, "/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact")
|
||||
|
||||
// get upload urls
|
||||
idx := strings.Index(uploadResp.SignedUploadUrl, "/twirp/")
|
||||
url := uploadResp.SignedUploadUrl[idx:] + "&comp=block&blockid=%2f..%2fmyfile"
|
||||
blockListURL := uploadResp.SignedUploadUrl[idx:] + "&comp=blocklist"
|
||||
url := uploadResp.SignedUploadUrl + "&comp=block&blockid=%2f..%2fmyfile"
|
||||
blockListURL := uploadResp.SignedUploadUrl + "&comp=blocklist"
|
||||
|
||||
// upload artifact chunk
|
||||
body := strings.Repeat("A", 1024)
|
||||
@@ -247,63 +374,126 @@ func TestActionsArtifactV4UploadSingleFileWithChunksOutOfOrder(t *testing.T) {
|
||||
token, err := actions_service.CreateAuthorizationToken(48, 792, 193)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// acquire artifact upload url
|
||||
req := NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/CreateArtifact", toProtoJSON(&actions.CreateArtifactRequest{
|
||||
Version: 4,
|
||||
Name: "artifactWithChunksOutOfOrder",
|
||||
WorkflowRunBackendId: "792",
|
||||
WorkflowJobRunBackendId: "193",
|
||||
})).AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
var uploadResp actions.CreateArtifactResponse
|
||||
protojson.Unmarshal(resp.Body.Bytes(), &uploadResp)
|
||||
assert.True(t, uploadResp.Ok)
|
||||
assert.Contains(t, uploadResp.SignedUploadUrl, "/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact")
|
||||
|
||||
// get upload urls
|
||||
idx := strings.Index(uploadResp.SignedUploadUrl, "/twirp/")
|
||||
block1URL := uploadResp.SignedUploadUrl[idx:] + "&comp=block&blockid=block1"
|
||||
block2URL := uploadResp.SignedUploadUrl[idx:] + "&comp=block&blockid=block2"
|
||||
blockListURL := uploadResp.SignedUploadUrl[idx:] + "&comp=blocklist"
|
||||
|
||||
// upload artifact chunks
|
||||
bodyb := strings.Repeat("B", 1024)
|
||||
req = NewRequestWithBody(t, "PUT", block2URL, strings.NewReader(bodyb))
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
bodya := strings.Repeat("A", 1024)
|
||||
req = NewRequestWithBody(t, "PUT", block1URL, strings.NewReader(bodya))
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
// upload artifact blockList
|
||||
blockList := &actions.BlockList{
|
||||
Latest: []string{
|
||||
"block1",
|
||||
"block2",
|
||||
},
|
||||
table := []struct {
|
||||
name string
|
||||
artifactName string
|
||||
serveDirect bool
|
||||
contentType string
|
||||
}{
|
||||
{name: "Upload-Zip", artifactName: "artifact-v4-upload", contentType: ""},
|
||||
{name: "Upload-Pdf", artifactName: "report-upload.pdf", contentType: "application/pdf"},
|
||||
{name: "Upload-Html", artifactName: "report-upload.html", contentType: "application/html"},
|
||||
{name: "ServeDirect-Zip", artifactName: "artifact-v4-upload-serve-direct", contentType: "", serveDirect: true},
|
||||
{name: "ServeDirect-Pdf", artifactName: "report-upload-serve-direct.pdf", contentType: "application/pdf", serveDirect: true},
|
||||
{name: "ServeDirect-Html", artifactName: "report-upload-serve-direct.html", contentType: "application/html", serveDirect: true},
|
||||
}
|
||||
rawBlockList, err := xml.Marshal(blockList)
|
||||
assert.NoError(t, err)
|
||||
req = NewRequestWithBody(t, "PUT", blockListURL, bytes.NewReader(rawBlockList))
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
t.Logf("Create artifact confirm")
|
||||
for _, entry := range table {
|
||||
t.Run(entry.name, func(t *testing.T) {
|
||||
// Only AzureBlobStorageType supports ServeDirect Uploads
|
||||
switch setting.Actions.ArtifactStorage.Type {
|
||||
case setting.AzureBlobStorageType:
|
||||
defer test.MockVariableValue(&setting.Actions.ArtifactStorage.AzureBlobConfig.ServeDirect, entry.serveDirect)()
|
||||
default:
|
||||
if entry.serveDirect {
|
||||
t.Skip()
|
||||
}
|
||||
}
|
||||
// acquire artifact upload url
|
||||
req := NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/CreateArtifact", toProtoJSON(&actions.CreateArtifactRequest{
|
||||
Version: util.Iif[int32](entry.contentType != "", 7, 4),
|
||||
Name: entry.artifactName,
|
||||
WorkflowRunBackendId: "792",
|
||||
WorkflowJobRunBackendId: "193",
|
||||
MimeType: util.Iif(entry.contentType != "", wrapperspb.String(entry.contentType), nil),
|
||||
})).AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
var uploadResp actions.CreateArtifactResponse
|
||||
protojson.Unmarshal(resp.Body.Bytes(), &uploadResp)
|
||||
assert.True(t, uploadResp.Ok)
|
||||
if !entry.serveDirect {
|
||||
assert.Contains(t, uploadResp.SignedUploadUrl, "/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact")
|
||||
}
|
||||
|
||||
sha := sha256.Sum256([]byte(bodya + bodyb))
|
||||
// get upload urls
|
||||
block1URL := uploadResp.SignedUploadUrl + "&comp=block&blockid=" + base64.RawURLEncoding.EncodeToString([]byte("block1"))
|
||||
block2URL := uploadResp.SignedUploadUrl + "&comp=block&blockid=" + base64.RawURLEncoding.EncodeToString([]byte("block2"))
|
||||
blockListURL := uploadResp.SignedUploadUrl + "&comp=blocklist"
|
||||
|
||||
// confirm artifact upload
|
||||
req = NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/FinalizeArtifact", toProtoJSON(&actions.FinalizeArtifactRequest{
|
||||
Name: "artifactWithChunksOutOfOrder",
|
||||
Size: 2048,
|
||||
Hash: wrapperspb.String("sha256:" + hex.EncodeToString(sha[:])),
|
||||
WorkflowRunBackendId: "792",
|
||||
WorkflowJobRunBackendId: "193",
|
||||
})).
|
||||
AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
var finalizeResp actions.FinalizeArtifactResponse
|
||||
protojson.Unmarshal(resp.Body.Bytes(), &finalizeResp)
|
||||
assert.True(t, finalizeResp.Ok)
|
||||
// upload artifact chunks
|
||||
bodyb := strings.Repeat("B", 1024)
|
||||
req = NewRequestWithBody(t, "PUT", block2URL, strings.NewReader(bodyb))
|
||||
if entry.serveDirect {
|
||||
req.Request.RequestURI = ""
|
||||
nresp, err := http.DefaultClient.Do(req.Request)
|
||||
require.NoError(t, err)
|
||||
nresp.Body.Close()
|
||||
require.Equal(t, http.StatusCreated, nresp.StatusCode)
|
||||
} else {
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
}
|
||||
|
||||
bodya := strings.Repeat("A", 1024)
|
||||
req = NewRequestWithBody(t, "PUT", block1URL, strings.NewReader(bodya))
|
||||
if entry.serveDirect {
|
||||
req.Request.RequestURI = ""
|
||||
nresp, err := http.DefaultClient.Do(req.Request)
|
||||
require.NoError(t, err)
|
||||
nresp.Body.Close()
|
||||
require.Equal(t, http.StatusCreated, nresp.StatusCode)
|
||||
} else {
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
}
|
||||
|
||||
// upload artifact blockList
|
||||
blockList := &actions.BlockList{
|
||||
Latest: []string{
|
||||
base64.RawURLEncoding.EncodeToString([]byte("block1")),
|
||||
base64.RawURLEncoding.EncodeToString([]byte("block2")),
|
||||
},
|
||||
}
|
||||
rawBlockList, err := xml.Marshal(blockList)
|
||||
assert.NoError(t, err)
|
||||
req = NewRequestWithBody(t, "PUT", blockListURL, bytes.NewReader(rawBlockList))
|
||||
if entry.serveDirect {
|
||||
req.Request.RequestURI = ""
|
||||
nresp, err := http.DefaultClient.Do(req.Request)
|
||||
require.NoError(t, err)
|
||||
nresp.Body.Close()
|
||||
require.Equal(t, http.StatusCreated, nresp.StatusCode)
|
||||
} else {
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
}
|
||||
|
||||
t.Logf("Create artifact confirm")
|
||||
|
||||
sha := sha256.Sum256([]byte(bodya + bodyb))
|
||||
|
||||
// confirm artifact upload
|
||||
req = NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/FinalizeArtifact", toProtoJSON(&actions.FinalizeArtifactRequest{
|
||||
Name: entry.artifactName,
|
||||
Size: 2048,
|
||||
Hash: wrapperspb.String("sha256:" + hex.EncodeToString(sha[:])),
|
||||
WorkflowRunBackendId: "792",
|
||||
WorkflowJobRunBackendId: "193",
|
||||
})).
|
||||
AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
var finalizeResp actions.FinalizeArtifactResponse
|
||||
protojson.Unmarshal(resp.Body.Bytes(), &finalizeResp)
|
||||
assert.True(t, finalizeResp.Ok)
|
||||
|
||||
artifact := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionArtifact{ID: finalizeResp.ArtifactId})
|
||||
if entry.contentType != "" {
|
||||
assert.Equal(t, entry.contentType, artifact.ContentEncodingOrType)
|
||||
} else {
|
||||
assert.Equal(t, "application/zip", artifact.ContentEncodingOrType)
|
||||
}
|
||||
assert.Equal(t, actions_model.ArtifactStatusUploadConfirmed, artifact.Status)
|
||||
assert.Equal(t, int64(2048), artifact.FileSize)
|
||||
assert.Equal(t, int64(2048), artifact.FileCompressedSize)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestActionsArtifactV4DownloadSingle(t *testing.T) {
|
||||
@@ -312,33 +502,97 @@ func TestActionsArtifactV4DownloadSingle(t *testing.T) {
|
||||
token, err := actions_service.CreateAuthorizationToken(48, 792, 193)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// acquire artifact upload url
|
||||
req := NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/ListArtifacts", toProtoJSON(&actions.ListArtifactsRequest{
|
||||
NameFilter: wrapperspb.String("artifact-v4-download"),
|
||||
WorkflowRunBackendId: "792",
|
||||
WorkflowJobRunBackendId: "193",
|
||||
})).AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
var listResp actions.ListArtifactsResponse
|
||||
protojson.Unmarshal(resp.Body.Bytes(), &listResp)
|
||||
assert.Len(t, listResp.Artifacts, 1)
|
||||
table := []struct {
|
||||
Name string
|
||||
ArtifactName string
|
||||
FileName string
|
||||
ServeDirect bool
|
||||
ContentType string
|
||||
ContentDisposition string
|
||||
}{
|
||||
{Name: "Download-Zip", ArtifactName: "artifact-v4-download", FileName: "artifact-v4-download.zip", ContentType: "application/zip"},
|
||||
{Name: "Download-Pdf", ArtifactName: "report.pdf", FileName: "report.pdf", ContentType: "application/pdf"},
|
||||
{Name: "Download-Html", ArtifactName: "report.html", FileName: "report.html", ContentType: "application/html"},
|
||||
{Name: "ServeDirect-Zip", ArtifactName: "artifact-v4-download", FileName: "artifact-v4-download.zip", ContentType: "application/zip", ServeDirect: true},
|
||||
{Name: "ServeDirect-Pdf", ArtifactName: "report.pdf", FileName: "report.pdf", ContentType: "application/pdf", ServeDirect: true},
|
||||
{Name: "ServeDirect-Html", ArtifactName: "report.html", FileName: "report.html", ContentType: "application/html", ServeDirect: true},
|
||||
}
|
||||
|
||||
// confirm artifact upload
|
||||
req = NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/GetSignedArtifactURL", toProtoJSON(&actions.GetSignedArtifactURLRequest{
|
||||
Name: "artifact-v4-download",
|
||||
WorkflowRunBackendId: "792",
|
||||
WorkflowJobRunBackendId: "193",
|
||||
})).
|
||||
AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
var finalizeResp actions.GetSignedArtifactURLResponse
|
||||
protojson.Unmarshal(resp.Body.Bytes(), &finalizeResp)
|
||||
assert.NotEmpty(t, finalizeResp.SignedUrl)
|
||||
for _, entry := range table {
|
||||
t.Run(entry.Name, func(t *testing.T) {
|
||||
switch setting.Actions.ArtifactStorage.Type {
|
||||
case setting.AzureBlobStorageType:
|
||||
defer test.MockVariableValue(&setting.Actions.ArtifactStorage.AzureBlobConfig.ServeDirect, entry.ServeDirect)()
|
||||
case setting.MinioStorageType:
|
||||
defer test.MockVariableValue(&setting.Actions.ArtifactStorage.MinioConfig.ServeDirect, entry.ServeDirect)()
|
||||
default:
|
||||
if entry.ServeDirect {
|
||||
t.Skip()
|
||||
}
|
||||
}
|
||||
|
||||
req = NewRequest(t, "GET", finalizeResp.SignedUrl)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
body := strings.Repeat("D", 1024)
|
||||
assert.Equal(t, body, resp.Body.String())
|
||||
// list artifacts by name
|
||||
req := NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/ListArtifacts", toProtoJSON(&actions.ListArtifactsRequest{
|
||||
NameFilter: wrapperspb.String(entry.ArtifactName),
|
||||
WorkflowRunBackendId: "792",
|
||||
WorkflowJobRunBackendId: "193",
|
||||
})).AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
var listResp actions.ListArtifactsResponse
|
||||
require.NoError(t, protojson.Unmarshal(resp.Body.Bytes(), &listResp))
|
||||
require.Len(t, listResp.Artifacts, 1)
|
||||
|
||||
// list artifacts by id
|
||||
req = NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/ListArtifacts", toProtoJSON(&actions.ListArtifactsRequest{
|
||||
IdFilter: wrapperspb.Int64(listResp.Artifacts[0].DatabaseId),
|
||||
WorkflowRunBackendId: "792",
|
||||
WorkflowJobRunBackendId: "193",
|
||||
})).AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
require.NoError(t, protojson.Unmarshal(resp.Body.Bytes(), &listResp))
|
||||
assert.Len(t, listResp.Artifacts, 1)
|
||||
|
||||
// acquire artifact download url
|
||||
req = NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/GetSignedArtifactURL", toProtoJSON(&actions.GetSignedArtifactURLRequest{
|
||||
Name: entry.ArtifactName,
|
||||
WorkflowRunBackendId: "792",
|
||||
WorkflowJobRunBackendId: "193",
|
||||
})).
|
||||
AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
var finalizeResp actions.GetSignedArtifactURLResponse
|
||||
require.NoError(t, protojson.Unmarshal(resp.Body.Bytes(), &finalizeResp))
|
||||
assert.NotEmpty(t, finalizeResp.SignedUrl)
|
||||
|
||||
body := strings.Repeat("D", 1024)
|
||||
var contentDisposition string
|
||||
if entry.ServeDirect {
|
||||
externalReq, err := http.NewRequestWithContext(t.Context(), http.MethodGet, finalizeResp.SignedUrl, nil)
|
||||
require.NoError(t, err)
|
||||
externalResp, err := http.DefaultClient.Do(externalReq)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, externalResp.StatusCode)
|
||||
assert.Equal(t, entry.ContentType, externalResp.Header.Get("Content-Type"))
|
||||
contentDisposition = externalResp.Header.Get("Content-Disposition")
|
||||
buf := make([]byte, 1024)
|
||||
n, err := io.ReadAtLeast(externalResp.Body, buf, len(buf))
|
||||
externalResp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, len(buf), n)
|
||||
assert.Equal(t, body, string(buf))
|
||||
} else {
|
||||
req = NewRequest(t, "GET", finalizeResp.SignedUrl)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
assert.Equal(t, entry.ContentType, resp.Header().Get("Content-Type"))
|
||||
contentDisposition = resp.Header().Get("Content-Disposition")
|
||||
assert.Equal(t, body, resp.Body.String())
|
||||
}
|
||||
disposition, param, err := mime.ParseMediaType(contentDisposition)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "inline", disposition)
|
||||
assert.Equal(t, entry.FileName, param["filename"])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestActionsArtifactV4RunDownloadSinglePublicApi(t *testing.T) {
|
||||
@@ -469,7 +723,7 @@ func TestActionsArtifactV4ListAndGetPublicApi(t *testing.T) {
|
||||
for _, artifact := range listResp.Entries {
|
||||
assert.Contains(t, artifact.URL, fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), artifact.ID))
|
||||
assert.Contains(t, artifact.ArchiveDownloadURL, fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d/zip", repo.FullName(), artifact.ID))
|
||||
req = NewRequestWithBody(t, "GET", listResp.Entries[0].URL, nil).
|
||||
req = NewRequestWithBody(t, "GET", artifact.URL, nil).
|
||||
AddTokenAuth(token)
|
||||
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func testActionUserSignIn(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/api/v1/user").
|
||||
AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var u api.User
|
||||
DecodeJSON(t, resp, &u)
|
||||
assert.Equal(t, "gitea-actions", u.UserName)
|
||||
}
|
||||
|
||||
func testActionUserAccessPublicRepo(t *testing.T) {
|
||||
req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/raw/README.md").
|
||||
AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
assert.Equal(t, "file", resp.Header().Get("x-gitea-object-type"))
|
||||
|
||||
defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)()
|
||||
|
||||
req = NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/raw/README.md").
|
||||
AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
assert.Equal(t, "file", resp.Header().Get("x-gitea-object-type"))
|
||||
}
|
||||
|
||||
func testActionUserNoAccessOtherPrivateRepo(t *testing.T) {
|
||||
req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo2/raw/README.md").
|
||||
AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func TestActionUserAccessPermission(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
t.Run("ActionUserSignIn", testActionUserSignIn)
|
||||
t.Run("ActionUserAccessPublicRepo", testActionUserAccessPublicRepo)
|
||||
t.Run("ActionUserNoAccessOtherPrivateRepo", testActionUserNoAccessOtherPrivateRepo)
|
||||
}
|
||||
@@ -6,16 +6,21 @@ package integration
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAPIActionsGetWorkflowRun(t *testing.T) {
|
||||
@@ -26,15 +31,45 @@ func TestAPIActionsGetWorkflowRun(t *testing.T) {
|
||||
session := loginUser(t, user.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/802802", repo.FullName())).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/802", repo.FullName())).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/803", repo.FullName())).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
t.Run("GetRun", func(t *testing.T) {
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/802802", repo.FullName())).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/802", repo.FullName())).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/803", repo.FullName())).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("GetJobSteps", func(t *testing.T) {
|
||||
// Insert task steps for task_id 53 (job 198) so the API can return them once the backend loads them
|
||||
_, err := db.GetEngine(t.Context()).Insert(&actions_model.ActionTaskStep{
|
||||
Name: "main",
|
||||
TaskID: 53,
|
||||
Index: 0,
|
||||
RepoID: repo.ID,
|
||||
Status: actions_model.StatusSuccess,
|
||||
Started: timeutil.TimeStamp(1683636528),
|
||||
Stopped: timeutil.TimeStamp(1683636626),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/jobs", repo.FullName())).
|
||||
AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var jobList api.ActionWorkflowJobsResponse
|
||||
err = json.Unmarshal(resp.Body.Bytes(), &jobList)
|
||||
require.NoError(t, err)
|
||||
|
||||
job198Idx := slices.IndexFunc(jobList.Entries, func(job *api.ActionWorkflowJob) bool { return job.ID == 198 })
|
||||
require.NotEqual(t, -1, job198Idx, "expected to find job 198 in run 795 jobs list")
|
||||
job198 := jobList.Entries[job198Idx]
|
||||
require.NotEmpty(t, job198.Steps, "job must return at least one step when task has steps")
|
||||
assert.Equal(t, "main", job198.Steps[0].Name, "first step name")
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIActionsGetWorkflowJob(t *testing.T) {
|
||||
@@ -134,3 +169,126 @@ func testAPIActionsDeleteRunListTasks(t *testing.T, repo *repo_model.Repository,
|
||||
assert.Equal(t, expected, findTask1)
|
||||
assert.Equal(t, expected, findTask2)
|
||||
}
|
||||
|
||||
func TestAPIActionsRerunWorkflowRun(t *testing.T) {
|
||||
defer prepareTestEnvActionsArtifacts(t)()
|
||||
|
||||
t.Run("NotDone", func(t *testing.T) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
session := loginUser(t, user.Name)
|
||||
writeToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/runs/793/rerun", repo.FullName())).
|
||||
AddTokenAuth(writeToken)
|
||||
MakeRequest(t, req, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
session := loginUser(t, user.Name)
|
||||
|
||||
writeToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
readToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
|
||||
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/rerun", repo.FullName())).
|
||||
AddTokenAuth(writeToken)
|
||||
resp := MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
var rerunResp api.ActionWorkflowRun
|
||||
err := json.Unmarshal(resp.Body.Bytes(), &rerunResp)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(795), rerunResp.ID)
|
||||
assert.Equal(t, "queued", rerunResp.Status)
|
||||
assert.Equal(t, "c2d72f548424103f01ee1dc02889c1e2bff816b0", rerunResp.HeadSha)
|
||||
|
||||
run, err := actions_model.GetRunByRepoAndID(t.Context(), repo.ID, 795)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, actions_model.StatusWaiting, run.Status)
|
||||
assert.Equal(t, timeutil.TimeStamp(0), run.Started)
|
||||
assert.Equal(t, timeutil.TimeStamp(0), run.Stopped)
|
||||
|
||||
job198, err := actions_model.GetRunJobByRunAndID(t.Context(), 795, 198)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, actions_model.StatusWaiting, job198.Status)
|
||||
assert.Equal(t, int64(0), job198.TaskID)
|
||||
|
||||
job199, err := actions_model.GetRunJobByRunAndID(t.Context(), 795, 199)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, actions_model.StatusWaiting, job199.Status)
|
||||
assert.Equal(t, int64(0), job199.TaskID)
|
||||
})
|
||||
|
||||
t.Run("ForbiddenWithoutWriteScope", func(t *testing.T) {
|
||||
req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/rerun", repo.FullName())).
|
||||
AddTokenAuth(readToken)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/runs/999999/rerun", repo.FullName())).
|
||||
AddTokenAuth(writeToken)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIActionsRerunWorkflowJob(t *testing.T) {
|
||||
defer prepareTestEnvActionsArtifacts(t)()
|
||||
|
||||
t.Run("NotDone", func(t *testing.T) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
session := loginUser(t, user.Name)
|
||||
writeToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/runs/793/jobs/194/rerun", repo.FullName())).
|
||||
AddTokenAuth(writeToken)
|
||||
MakeRequest(t, req, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
session := loginUser(t, user.Name)
|
||||
|
||||
writeToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
readToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
|
||||
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/jobs/199/rerun", repo.FullName())).
|
||||
AddTokenAuth(writeToken)
|
||||
resp := MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
var rerunResp api.ActionWorkflowJob
|
||||
err := json.Unmarshal(resp.Body.Bytes(), &rerunResp)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(199), rerunResp.ID)
|
||||
assert.Equal(t, "queued", rerunResp.Status)
|
||||
|
||||
run, err := actions_model.GetRunByRepoAndID(t.Context(), repo.ID, 795)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, actions_model.StatusWaiting, run.Status)
|
||||
|
||||
job198, err := actions_model.GetRunJobByRunAndID(t.Context(), 795, 198)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, actions_model.StatusSuccess, job198.Status)
|
||||
assert.Equal(t, int64(53), job198.TaskID)
|
||||
|
||||
job199, err := actions_model.GetRunJobByRunAndID(t.Context(), 795, 199)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, actions_model.StatusWaiting, job199.Status)
|
||||
assert.Equal(t, int64(0), job199.TaskID)
|
||||
})
|
||||
|
||||
t.Run("ForbiddenWithoutWriteScope", func(t *testing.T) {
|
||||
req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/jobs/199/rerun", repo.FullName())).
|
||||
AddTokenAuth(readToken)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("NotFoundJob", func(t *testing.T) {
|
||||
req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/jobs/999999/rerun", repo.FullName())).
|
||||
AddTokenAuth(writeToken)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -24,6 +24,12 @@ func TestAPIActionsRunner(t *testing.T) {
|
||||
t.Run("RepoRunner", testActionsRunnerRepo)
|
||||
}
|
||||
|
||||
func newRunnerUpdateRequest(t *testing.T, url string, disabled bool) *RequestWrapper {
|
||||
return NewRequestWithJSON(t, http.MethodPatch, url, api.EditActionRunnerOption{
|
||||
Disabled: &disabled,
|
||||
})
|
||||
}
|
||||
|
||||
func testActionsRunnerAdmin(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
adminUsername := "user1"
|
||||
@@ -45,22 +51,39 @@ func testActionsRunnerAdmin(t *testing.T) {
|
||||
require.NotEqual(t, -1, idx)
|
||||
expectedRunner := runnerList.Entries[idx]
|
||||
assert.Equal(t, "runner_to_be_deleted", expectedRunner.Name)
|
||||
assert.False(t, expectedRunner.Disabled)
|
||||
assert.False(t, expectedRunner.Ephemeral)
|
||||
assert.Len(t, expectedRunner.Labels, 2)
|
||||
assert.Equal(t, "runner_to_be_deleted", expectedRunner.Labels[0].Name)
|
||||
assert.Equal(t, "linux", expectedRunner.Labels[1].Name)
|
||||
|
||||
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/admin/actions/runners/%d", expectedRunner.ID), true).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/admin/actions/runners/%d", expectedRunner.ID), true).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/admin/actions/runners/%d", expectedRunner.ID)).AddTokenAuth(token)
|
||||
runnerResp := MakeRequest(t, req, http.StatusOK)
|
||||
disabledRunner := api.ActionRunner{}
|
||||
DecodeJSON(t, runnerResp, &disabledRunner)
|
||||
assert.True(t, disabledRunner.Disabled)
|
||||
|
||||
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/admin/actions/runners/%d", expectedRunner.ID), false).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/admin/actions/runners/%d", expectedRunner.ID), false).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// Verify all returned runners can be requested and deleted
|
||||
for _, runnerEntry := range runnerList.Entries {
|
||||
// Verify get the runner by id
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/admin/actions/runners/%d", runnerEntry.ID)).AddTokenAuth(token)
|
||||
runnerResp := MakeRequest(t, req, http.StatusOK)
|
||||
runnerResp = MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
runner := api.ActionRunner{}
|
||||
DecodeJSON(t, runnerResp, &runner)
|
||||
|
||||
assert.Equal(t, runnerEntry.Name, runner.Name)
|
||||
assert.Equal(t, runnerEntry.ID, runner.ID)
|
||||
assert.Equal(t, runnerEntry.Disabled, runner.Disabled)
|
||||
assert.Equal(t, runnerEntry.Ephemeral, runner.Ephemeral)
|
||||
assert.ElementsMatch(t, runnerEntry.Labels, runner.Labels)
|
||||
|
||||
@@ -93,6 +116,7 @@ func testActionsRunnerUser(t *testing.T) {
|
||||
assert.Len(t, runnerList.Entries, 1)
|
||||
assert.Equal(t, "runner_to_be_deleted-user", runnerList.Entries[0].Name)
|
||||
assert.Equal(t, int64(34346), runnerList.Entries[0].ID)
|
||||
assert.False(t, runnerList.Entries[0].Disabled)
|
||||
assert.False(t, runnerList.Entries[0].Ephemeral)
|
||||
assert.Len(t, runnerList.Entries[0].Labels, 2)
|
||||
assert.Equal(t, "runner_to_be_deleted", runnerList.Entries[0].Labels[0].Name)
|
||||
@@ -107,11 +131,26 @@ func testActionsRunnerUser(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "runner_to_be_deleted-user", runner.Name)
|
||||
assert.Equal(t, int64(34346), runner.ID)
|
||||
assert.False(t, runner.Disabled)
|
||||
assert.False(t, runner.Ephemeral)
|
||||
assert.Len(t, runner.Labels, 2)
|
||||
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
||||
assert.Equal(t, "linux", runner.Labels[1].Name)
|
||||
|
||||
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID), true).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID), true).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||
runnerResp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, runnerResp, &runner)
|
||||
assert.True(t, runner.Disabled)
|
||||
|
||||
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID), false).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID), false).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// Verify delete the runner by id
|
||||
req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
@@ -136,6 +175,7 @@ func testActionsRunnerOwner(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "runner_to_be_deleted-org", runner.Name)
|
||||
assert.Equal(t, int64(34347), runner.ID)
|
||||
assert.False(t, runner.Disabled)
|
||||
assert.False(t, runner.Ephemeral)
|
||||
assert.Len(t, runner.Labels, 2)
|
||||
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
||||
@@ -165,6 +205,7 @@ func testActionsRunnerOwner(t *testing.T) {
|
||||
require.NotNil(t, expectedRunner)
|
||||
assert.Equal(t, "runner_to_be_deleted-org", expectedRunner.Name)
|
||||
assert.Equal(t, int64(34347), expectedRunner.ID)
|
||||
assert.False(t, expectedRunner.Disabled)
|
||||
assert.False(t, expectedRunner.Ephemeral)
|
||||
assert.Len(t, expectedRunner.Labels, 2)
|
||||
assert.Equal(t, "runner_to_be_deleted", expectedRunner.Labels[0].Name)
|
||||
@@ -179,11 +220,26 @@ func testActionsRunnerOwner(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "runner_to_be_deleted-org", runner.Name)
|
||||
assert.Equal(t, int64(34347), runner.ID)
|
||||
assert.False(t, runner.Disabled)
|
||||
assert.False(t, runner.Ephemeral)
|
||||
assert.Len(t, runner.Labels, 2)
|
||||
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
||||
assert.Equal(t, "linux", runner.Labels[1].Name)
|
||||
|
||||
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID), true).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID), true).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID)).AddTokenAuth(token)
|
||||
runnerResp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, runnerResp, &runner)
|
||||
assert.True(t, runner.Disabled)
|
||||
|
||||
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID), false).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID), false).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// Verify delete the runner by id
|
||||
req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID)).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
@@ -202,6 +258,22 @@ func testActionsRunnerOwner(t *testing.T) {
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("DisableReadScopeForbidden", func(t *testing.T) {
|
||||
userUsername := "user2"
|
||||
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
|
||||
|
||||
req := newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34347), true).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("UpdateReadScopeForbidden", func(t *testing.T) {
|
||||
userUsername := "user2"
|
||||
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
|
||||
|
||||
req := newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34347), false).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("GetRepoScopeForbidden", func(t *testing.T) {
|
||||
userUsername := "user2"
|
||||
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
|
||||
@@ -244,6 +316,7 @@ func testActionsRunnerRepo(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "runner_to_be_deleted-repo1", runner.Name)
|
||||
assert.Equal(t, int64(34348), runner.ID)
|
||||
assert.False(t, runner.Disabled)
|
||||
assert.False(t, runner.Ephemeral)
|
||||
assert.Len(t, runner.Labels, 2)
|
||||
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
||||
@@ -269,6 +342,7 @@ func testActionsRunnerRepo(t *testing.T) {
|
||||
assert.Len(t, runnerList.Entries, 1)
|
||||
assert.Equal(t, "runner_to_be_deleted-repo1", runnerList.Entries[0].Name)
|
||||
assert.Equal(t, int64(34348), runnerList.Entries[0].ID)
|
||||
assert.False(t, runnerList.Entries[0].Disabled)
|
||||
assert.False(t, runnerList.Entries[0].Ephemeral)
|
||||
assert.Len(t, runnerList.Entries[0].Labels, 2)
|
||||
assert.Equal(t, "runner_to_be_deleted", runnerList.Entries[0].Labels[0].Name)
|
||||
@@ -283,11 +357,26 @@ func testActionsRunnerRepo(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "runner_to_be_deleted-repo1", runner.Name)
|
||||
assert.Equal(t, int64(34348), runner.ID)
|
||||
assert.False(t, runner.Disabled)
|
||||
assert.False(t, runner.Ephemeral)
|
||||
assert.Len(t, runner.Labels, 2)
|
||||
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
||||
assert.Equal(t, "linux", runner.Labels[1].Name)
|
||||
|
||||
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID), true).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID), true).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||
runnerResp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, runnerResp, &runner)
|
||||
assert.True(t, runner.Disabled)
|
||||
|
||||
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID), false).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID), false).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// Verify delete the runner by id
|
||||
req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
@@ -306,6 +395,22 @@ func testActionsRunnerRepo(t *testing.T) {
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("DisableReadScopeForbidden", func(t *testing.T) {
|
||||
userUsername := "user2"
|
||||
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
|
||||
|
||||
req := newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34348), true).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("UpdateReadScopeForbidden", func(t *testing.T) {
|
||||
userUsername := "user2"
|
||||
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
|
||||
|
||||
req := newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34348), false).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("GetOrganizationScopeForbidden", func(t *testing.T) {
|
||||
userUsername := "user2"
|
||||
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/activitypub"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/routers"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
ap "github.com/go-ap/activitypub"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestActivityPubPerson(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
defer test.MockVariableValue(&setting.Federation.Enabled, true)()
|
||||
defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())()
|
||||
|
||||
t.Run("ExistingPerson", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
userID := 2
|
||||
username := "user2"
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/activitypub/user-id/%v", userID))
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
body := resp.Body.Bytes()
|
||||
assert.Contains(t, string(body), "@context")
|
||||
|
||||
var person ap.Person
|
||||
err := person.UnmarshalJSON(body)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, ap.PersonType, person.Type)
|
||||
assert.Equal(t, username, person.PreferredUsername.String())
|
||||
keyID := person.GetID().String()
|
||||
assert.Regexp(t, fmt.Sprintf("activitypub/user-id/%v$", userID), keyID)
|
||||
assert.Regexp(t, fmt.Sprintf("activitypub/user-id/%v/outbox$", userID), person.Outbox.GetID().String())
|
||||
assert.Regexp(t, fmt.Sprintf("activitypub/user-id/%v/inbox$", userID), person.Inbox.GetID().String())
|
||||
|
||||
pubKey := person.PublicKey
|
||||
assert.NotNil(t, pubKey)
|
||||
publicKeyID := keyID + "#main-key"
|
||||
assert.Equal(t, pubKey.ID.String(), publicKeyID)
|
||||
|
||||
pubKeyPem := pubKey.PublicKeyPem
|
||||
assert.NotNil(t, pubKeyPem)
|
||||
assert.Regexp(t, "^-----BEGIN PUBLIC KEY-----", pubKeyPem)
|
||||
})
|
||||
t.Run("MissingPerson", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
req := NewRequest(t, "GET", "/api/v1/activitypub/user-id/999999999")
|
||||
resp := MakeRequest(t, req, http.StatusNotFound)
|
||||
assert.Contains(t, resp.Body.String(), "user does not exist")
|
||||
})
|
||||
t.Run("MissingPersonInbox", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
srv := httptest.NewServer(testWebRoutes)
|
||||
defer srv.Close()
|
||||
defer test.MockVariableValue(&setting.AppURL, srv.URL+"/")()
|
||||
|
||||
username1 := "user1"
|
||||
ctx := t.Context()
|
||||
user1, err := user_model.GetUserByName(ctx, username1)
|
||||
assert.NoError(t, err)
|
||||
user1url := srv.URL + "/api/v1/activitypub/user-id/1#main-key"
|
||||
c, err := activitypub.NewClient(t.Context(), user1, user1url)
|
||||
assert.NoError(t, err)
|
||||
user2inboxurl := srv.URL + "/api/v1/activitypub/user-id/2/inbox"
|
||||
|
||||
// Signed request succeeds
|
||||
resp, err := c.Post([]byte{}, user2inboxurl)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusNoContent, resp.StatusCode)
|
||||
|
||||
// Unsigned request fails
|
||||
req := NewRequest(t, "POST", user2inboxurl)
|
||||
MakeRequest(t, req, http.StatusInternalServerError)
|
||||
})
|
||||
}
|
||||
@@ -4,6 +4,8 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
@@ -235,7 +237,40 @@ func TestAPIRenameBranch(t *testing.T) {
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
resp := testAPIRenameBranch(t, "user2", "user2", "repo1", from, "new-branch-name", http.StatusForbidden)
|
||||
assert.Contains(t, resp.Body.String(), "Branch is protected by glob-based protection rules.")
|
||||
assert.Contains(t, resp.Body.String(), "Failed to rename branch due to branch protection rules.")
|
||||
})
|
||||
t.Run("RenameBranchToMatchProtectionRulesWithAllowedUser", func(t *testing.T) {
|
||||
// allow an admin (the owner in this case) to rename a regular branch to one that matches a branch protection rule
|
||||
repoName := "repo1"
|
||||
ownerName := "user2"
|
||||
from := "regular-branch-1"
|
||||
ctx := NewAPITestContext(t, ownerName, repoName, auth_model.AccessTokenScopeWriteRepository)
|
||||
testAPICreateBranch(t, ctx.Session, ownerName, repoName, "", from, http.StatusCreated)
|
||||
|
||||
// NOTE: The protected/** branch protection rule was created in a previous test, with push enabled.
|
||||
testAPIRenameBranch(t, ownerName, ownerName, repoName, from, "protected/2", http.StatusNoContent)
|
||||
})
|
||||
t.Run("RenameBranchToMatchProtectionRulesWithUnauthorizedUser", func(t *testing.T) {
|
||||
// don't allow renaming a regular branch to a protected branch if the doer is not in the push whitelist
|
||||
repoName := "repo1"
|
||||
ownerName := "user2"
|
||||
pushWhitelist := []string{ownerName}
|
||||
token := getUserToken(t, "user2", auth_model.AccessTokenScopeWriteRepository)
|
||||
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/branch_protections", ownerName, repoName),
|
||||
&api.BranchProtection{
|
||||
RuleName: "owner-protected/**",
|
||||
PushWhitelistUsernames: pushWhitelist,
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
from := "regular-branch-2"
|
||||
ctx := NewAPITestContext(t, ownerName, repoName, auth_model.AccessTokenScopeWriteRepository)
|
||||
testAPICreateBranch(t, ctx.Session, ownerName, repoName, "", from, http.StatusCreated)
|
||||
|
||||
unprivilegedUser := "user40"
|
||||
resp := testAPIRenameBranch(t, unprivilegedUser, ownerName, repoName, from, "owner-protected/1", http.StatusForbidden)
|
||||
|
||||
assert.Contains(t, resp.Body.String(), "Failed to rename branch due to branch protection rules.")
|
||||
})
|
||||
t.Run("RenameBranchNormalScenario", func(t *testing.T) {
|
||||
testAPIRenameBranch(t, "user2", "user2", "repo1", "branch2", "new-branch-name", http.StatusNoContent)
|
||||
@@ -243,6 +278,79 @@ func TestAPIRenameBranch(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIUpdateBranchReference(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
ctx := NewAPITestContext(t, "user2", "update-branch", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
giteaURL.Path = ctx.GitPath()
|
||||
|
||||
var defaultBranch string
|
||||
t.Run("CreateRepo", doAPICreateRepository(ctx, false, func(t *testing.T, repo api.Repository) {
|
||||
defaultBranch = repo.DefaultBranch
|
||||
}))
|
||||
|
||||
createBranchReq := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/branches", ctx.Username, ctx.Reponame), &api.CreateBranchRepoOption{
|
||||
BranchName: "feature",
|
||||
OldRefName: defaultBranch,
|
||||
}).AddTokenAuth(ctx.Token)
|
||||
ctx.Session.MakeRequest(t, createBranchReq, http.StatusCreated)
|
||||
|
||||
var featureInitialCommit string
|
||||
t.Run("LoadFeatureBranch", doAPIGetBranch(ctx, "feature", func(t *testing.T, branch api.Branch) {
|
||||
featureInitialCommit = branch.Commit.ID
|
||||
assert.NotEmpty(t, featureInitialCommit)
|
||||
}))
|
||||
|
||||
content := base64.StdEncoding.EncodeToString([]byte("branch update test"))
|
||||
var newCommit string
|
||||
doAPICreateFile(ctx, "docs/update.txt", &api.CreateFileOptions{
|
||||
FileOptions: api.FileOptions{
|
||||
BranchName: defaultBranch,
|
||||
NewBranchName: defaultBranch,
|
||||
Message: "add docs/update.txt",
|
||||
},
|
||||
ContentBase64: content,
|
||||
}, func(t *testing.T, resp api.FileResponse) {
|
||||
newCommit = resp.Commit.SHA
|
||||
assert.NotEmpty(t, newCommit)
|
||||
})(t)
|
||||
|
||||
updateReq := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, "feature"), &api.UpdateBranchRepoOption{
|
||||
NewCommitID: newCommit,
|
||||
OldCommitID: featureInitialCommit,
|
||||
}).AddTokenAuth(ctx.Token)
|
||||
ctx.Session.MakeRequest(t, updateReq, http.StatusNoContent)
|
||||
|
||||
t.Run("FastForwardApplied", doAPIGetBranch(ctx, "feature", func(t *testing.T, branch api.Branch) {
|
||||
assert.Equal(t, newCommit, branch.Commit.ID)
|
||||
}))
|
||||
|
||||
staleReq := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, "feature"), &api.UpdateBranchRepoOption{
|
||||
NewCommitID: newCommit,
|
||||
OldCommitID: featureInitialCommit,
|
||||
}).AddTokenAuth(ctx.Token)
|
||||
ctx.Session.MakeRequest(t, staleReq, http.StatusUnprocessableEntity)
|
||||
|
||||
nonFFReq := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, "feature"), &api.UpdateBranchRepoOption{
|
||||
NewCommitID: featureInitialCommit,
|
||||
OldCommitID: newCommit,
|
||||
}).AddTokenAuth(ctx.Token)
|
||||
ctx.Session.MakeRequest(t, nonFFReq, http.StatusUnprocessableEntity)
|
||||
|
||||
forceReq := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, "feature"), &api.UpdateBranchRepoOption{
|
||||
NewCommitID: featureInitialCommit,
|
||||
OldCommitID: newCommit,
|
||||
Force: true,
|
||||
}).AddTokenAuth(ctx.Token)
|
||||
ctx.Session.MakeRequest(t, forceReq, http.StatusNoContent)
|
||||
|
||||
t.Run("ForceApplied", doAPIGetBranch(ctx, "feature", func(t *testing.T, branch api.Branch) {
|
||||
assert.Equal(t, featureInitialCommit, branch.Commit.ID)
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
func testAPIRenameBranch(t *testing.T, doerName, ownerName, repoName, from, to string, expectedHTTPStatus int) *httptest.ResponseRecorder {
|
||||
token := getUserToken(t, doerName, auth_model.AccessTokenScopeWriteRepository)
|
||||
req := NewRequestWithJSON(t, "PATCH", "api/v1/repos/"+ownerName+"/"+repoName+"/branches/"+from, &api.RenameBranchRepoOption{
|
||||
@@ -254,15 +362,20 @@ func testAPIRenameBranch(t *testing.T, doerName, ownerName, repoName, from, to s
|
||||
func TestAPIBranchProtection(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
// Branch protection on branch that not exist
|
||||
testAPICreateBranchProtection(t, "master/doesnotexist", 1, http.StatusCreated)
|
||||
// Can create branch protection on branch that not exist
|
||||
testAPICreateBranchProtection(t, "non-existing/branch", 1, http.StatusCreated)
|
||||
testAPIGetBranchProtection(t, "non-existing/branch", http.StatusOK)
|
||||
testAPIDeleteBranchProtection(t, "non-existing/branch", http.StatusNoContent)
|
||||
|
||||
// Get branch protection on branch that exist but not branch protection
|
||||
testAPIGetBranchProtection(t, "master", http.StatusNotFound)
|
||||
|
||||
testAPICreateBranchProtection(t, "master", 2, http.StatusCreated)
|
||||
testAPICreateBranchProtection(t, "master", 1, http.StatusCreated)
|
||||
// Can only create once
|
||||
testAPICreateBranchProtection(t, "master", 0, http.StatusForbidden)
|
||||
|
||||
testAPICreateBranchProtection(t, "other-branch", 2, http.StatusCreated)
|
||||
|
||||
// Can't delete a protected branch
|
||||
testAPIDeleteBranch(t, "master", http.StatusForbidden)
|
||||
|
||||
@@ -294,6 +407,8 @@ func TestAPIBranchProtection(t *testing.T) {
|
||||
// Test branch deletion
|
||||
testAPIDeleteBranch(t, "master", http.StatusForbidden)
|
||||
testAPIDeleteBranch(t, "branch2", http.StatusNoContent)
|
||||
testAPIDeleteBranch(t, "branch2", http.StatusNotFound) // deleted branch, there is a record in DB with IsDelete=true
|
||||
testAPIDeleteBranch(t, "no-such-branch", http.StatusNotFound) // non-existing branch, not exist in git or DB
|
||||
}
|
||||
|
||||
func TestAPICreateBranchWithSyncBranches(t *testing.T) {
|
||||
@@ -303,7 +418,7 @@ func TestAPICreateBranchWithSyncBranches(t *testing.T) {
|
||||
RepoID: 1,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, branches, 6)
|
||||
assert.Len(t, branches, 8)
|
||||
|
||||
// make a broke repository with no branch on database
|
||||
_, err = db.DeleteByBean(t.Context(), git_model.Branch{RepoID: 1})
|
||||
@@ -320,7 +435,7 @@ func TestAPICreateBranchWithSyncBranches(t *testing.T) {
|
||||
RepoID: 1,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, branches, 7)
|
||||
assert.Len(t, branches, 9)
|
||||
|
||||
branches, err = db.Find[git_model.Branch](t.Context(), git_model.FindBranchOptions{
|
||||
RepoID: 1,
|
||||
|
||||
@@ -19,15 +19,35 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCreateForkNoLogin(t *testing.T) {
|
||||
func TestAPIFork(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
t.Run("CreateForkNoLogin", testCreateForkNoLogin)
|
||||
t.Run("CreateForkOrgNoCreatePermission", testCreateForkOrgNoCreatePermission)
|
||||
t.Run("APIForkListLimitedAndPrivateRepos", testAPIForkListLimitedAndPrivateRepos)
|
||||
t.Run("GetPrivateReposForks", testGetPrivateReposForks)
|
||||
}
|
||||
|
||||
func testCreateForkNoLogin(t *testing.T) {
|
||||
req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/forks", &api.CreateForkOption{})
|
||||
MakeRequest(t, req, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
func TestAPIForkListLimitedAndPrivateRepos(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
func testCreateForkOrgNoCreatePermission(t *testing.T) {
|
||||
user4Sess := loginUser(t, "user4")
|
||||
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
|
||||
|
||||
canCreate, err := org_model.OrgFromUser(org).CanCreateOrgRepo(t.Context(), 4)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, canCreate)
|
||||
|
||||
user4Token := getTokenForLoggedInUser(t, user4Sess, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteOrganization)
|
||||
req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/forks", &api.CreateForkOption{
|
||||
Organization: &org.Name,
|
||||
}).AddTokenAuth(user4Token)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
}
|
||||
|
||||
func testAPIForkListLimitedAndPrivateRepos(t *testing.T) {
|
||||
user1Sess := loginUser(t, "user1")
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user1"})
|
||||
|
||||
@@ -64,10 +84,7 @@ func TestAPIForkListLimitedAndPrivateRepos(t *testing.T) {
|
||||
|
||||
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/forks")
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var forks []*api.Repository
|
||||
DecodeJSON(t, resp, &forks)
|
||||
|
||||
forks := DecodeJSON(t, resp, []*api.Repository{})
|
||||
assert.Empty(t, forks)
|
||||
assert.Equal(t, "0", resp.Header().Get("X-Total-Count"))
|
||||
})
|
||||
@@ -78,9 +95,7 @@ func TestAPIForkListLimitedAndPrivateRepos(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/forks").AddTokenAuth(user1Token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var forks []*api.Repository
|
||||
DecodeJSON(t, resp, &forks)
|
||||
|
||||
forks := DecodeJSON(t, resp, []*api.Repository{})
|
||||
assert.Len(t, forks, 2)
|
||||
assert.Equal(t, "2", resp.Header().Get("X-Total-Count"))
|
||||
|
||||
@@ -88,28 +103,22 @@ func TestAPIForkListLimitedAndPrivateRepos(t *testing.T) {
|
||||
|
||||
req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/forks").AddTokenAuth(user1Token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
forks = []*api.Repository{}
|
||||
DecodeJSON(t, resp, &forks)
|
||||
|
||||
forks = DecodeJSON(t, resp, []*api.Repository{})
|
||||
assert.Len(t, forks, 2)
|
||||
assert.Equal(t, "2", resp.Header().Get("X-Total-Count"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetPrivateReposForks(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
func testGetPrivateReposForks(t *testing.T) {
|
||||
user1Sess := loginUser(t, "user1")
|
||||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) // private repository
|
||||
privateOrg := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 23})
|
||||
user1Token := getTokenForLoggedInUser(t, user1Sess, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
forkedRepoName := "forked-repo"
|
||||
// create fork from a private repository
|
||||
req := NewRequestWithJSON(t, "POST", "/api/v1/repos/"+repo2.FullName()+"/forks", &api.CreateForkOption{
|
||||
Organization: &privateOrg.Name,
|
||||
Name: &forkedRepoName,
|
||||
Name: new("forked-repo"),
|
||||
}).AddTokenAuth(user1Token)
|
||||
MakeRequest(t, req, http.StatusAccepted)
|
||||
|
||||
@@ -117,8 +126,7 @@ func TestGetPrivateReposForks(t *testing.T) {
|
||||
req = NewRequest(t, "GET", "/api/v1/repos/"+repo2.FullName()+"/forks").AddTokenAuth(user1Token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
forks := []*api.Repository{}
|
||||
DecodeJSON(t, resp, &forks)
|
||||
forks := DecodeJSON(t, resp, []*api.Repository{})
|
||||
assert.Len(t, forks, 1)
|
||||
assert.Equal(t, "1", resp.Header().Get("X-Total-Count"))
|
||||
assert.Equal(t, "forked-repo", forks[0].Name)
|
||||
|
||||
@@ -63,8 +63,8 @@ func TestGPGKeys(t *testing.T) {
|
||||
t.Run("CreateInvalidGPGKey", func(t *testing.T) {
|
||||
testCreateInvalidGPGKey(t, tc.makeRequest, tc.token, tc.results[4])
|
||||
})
|
||||
t.Run("CreateNoneRegistredEmailGPGKey", func(t *testing.T) {
|
||||
testCreateNoneRegistredEmailGPGKey(t, tc.makeRequest, tc.token, tc.results[5])
|
||||
t.Run("CreateNoneRegisteredEmailGPGKey", func(t *testing.T) {
|
||||
testCreateNoneRegisteredEmailGPGKey(t, tc.makeRequest, tc.token, tc.results[5])
|
||||
})
|
||||
t.Run("CreateValidGPGKey", func(t *testing.T) {
|
||||
testCreateValidGPGKey(t, tc.makeRequest, tc.token, tc.results[6])
|
||||
@@ -179,7 +179,7 @@ func testCreateInvalidGPGKey(t *testing.T, makeRequest makeRequestFunc, token st
|
||||
testCreateGPGKey(t, makeRequest, token, expected, "invalid_key")
|
||||
}
|
||||
|
||||
func testCreateNoneRegistredEmailGPGKey(t *testing.T, makeRequest makeRequestFunc, token string, expected int) {
|
||||
func testCreateNoneRegisteredEmailGPGKey(t *testing.T, makeRequest makeRequestFunc, token string, expected int) {
|
||||
testCreateGPGKey(t, makeRequest, token, expected, `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQENBFmGUygBCACjCNbKvMGgp0fd5vyFW9olE1CLCSyyF9gQN2hSuzmZLuAZF2Kh
|
||||
|
||||
@@ -95,9 +95,7 @@ func TestHTTPSigCert(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
session := loginUser(t, "user1")
|
||||
|
||||
csrf := GetUserCSRFToken(t, session)
|
||||
req := NewRequestWithValues(t, "POST", "/user/settings/keys", map[string]string{
|
||||
"_csrf": csrf,
|
||||
"content": "user1",
|
||||
"title": "principal",
|
||||
"type": "principal",
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
@@ -179,3 +180,19 @@ func TestAPIRepoValidateIssueConfig(t *testing.T) {
|
||||
assert.NotEmpty(t, issueConfigValidation.Message)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIRepoIssueConfigRequiresCodeUnit(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 24})
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadRepository)
|
||||
|
||||
for _, path := range []string{
|
||||
fmt.Sprintf("/api/v1/repos/%s/issue_config", repo.FullName()),
|
||||
fmt.Sprintf("/api/v1/repos/%s/issue_config/validate", repo.FullName()),
|
||||
} {
|
||||
req := NewRequest(t, "GET", path).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func enableRepoDependencies(t *testing.T, repoID int64) {
|
||||
|
||||
repoUnit := unittest.AssertExistsAndLoadBean(t, &repo_model.RepoUnit{RepoID: repoID, Type: unit.TypeIssues})
|
||||
repoUnit.IssuesConfig().EnableDependencies = true
|
||||
assert.NoError(t, repo_model.UpdateRepoUnit(t.Context(), repoUnit))
|
||||
assert.NoError(t, repo_model.UpdateRepoUnitConfig(t.Context(), repoUnit))
|
||||
}
|
||||
|
||||
func TestAPICreateIssueDependencyCrossRepoPermission(t *testing.T) {
|
||||
|
||||
@@ -44,7 +44,7 @@ func TestAPIIssuesReactions(t *testing.T) {
|
||||
req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{
|
||||
Reaction: "zzz",
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// Add allowed reaction
|
||||
req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{
|
||||
@@ -55,7 +55,10 @@ func TestAPIIssuesReactions(t *testing.T) {
|
||||
DecodeJSON(t, resp, &apiNewReaction)
|
||||
|
||||
// Add existing reaction
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{
|
||||
Reaction: "rocket",
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// Blocked user can't react to comment
|
||||
user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34})
|
||||
@@ -111,7 +114,7 @@ func TestAPICommentReactions(t *testing.T) {
|
||||
req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{
|
||||
Reaction: "eyes",
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
t.Run("UnrelatedCommentID", func(t *testing.T) {
|
||||
// Using the ID of a comment that does not belong to the repository must fail
|
||||
@@ -142,7 +145,10 @@ func TestAPICommentReactions(t *testing.T) {
|
||||
DecodeJSON(t, resp, &apiNewReaction)
|
||||
|
||||
// Add existing reaction
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{
|
||||
Reaction: "+1",
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// Get end result of reaction list of issue #1
|
||||
req = NewRequest(t, "GET", urlStr).
|
||||
|
||||
@@ -8,10 +8,12 @@ import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -53,3 +55,19 @@ about: bar
|
||||
assert.Equal(t, "error occurs when parsing issue template: count=2", resp.Header().Get("X-Gitea-Warning"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIIssueTemplateRequiresCodeUnit(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 24})
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadRepository)
|
||||
issueTemplatesURL := "/api/v1/repos/" + repo.FullName() + "/issue_templates"
|
||||
languagesURL := "/api/v1/repos/" + repo.FullName() + "/languages"
|
||||
|
||||
req := NewRequest(t, "GET", issueTemplatesURL).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
|
||||
req = NewRequest(t, "GET", languagesURL).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
}
|
||||
|
||||
@@ -19,14 +19,25 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAPIListIssues(t *testing.T) {
|
||||
func TestAPIIssue(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
t.Run("ListIssues", testAPIListIssues)
|
||||
t.Run("ListIssuesPublicOnly", testAPIListIssuesPublicOnly)
|
||||
t.Run("SearchIssues", testAPISearchIssues)
|
||||
t.Run("SearchIssuesWithLabels", testAPISearchIssuesWithLabels)
|
||||
t.Run("EditIssue", testAPIEditIssue)
|
||||
t.Run("IssueContentVersion", testAPIIssueContentVersion)
|
||||
t.Run("CreateIssue", testAPICreateIssue)
|
||||
t.Run("CreateIssueParallel", testAPICreateIssueParallel)
|
||||
}
|
||||
|
||||
func testAPIListIssues(t *testing.T) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
|
||||
@@ -74,9 +85,7 @@ func TestAPIListIssues(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIListIssuesPublicOnly(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
func testAPIListIssuesPublicOnly(t *testing.T) {
|
||||
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
owner1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo1.OwnerID})
|
||||
|
||||
@@ -99,11 +108,10 @@ func TestAPIListIssuesPublicOnly(t *testing.T) {
|
||||
|
||||
publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopePublicOnly)
|
||||
req = NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func TestAPICreateIssue(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
func testAPICreateIssue(t *testing.T) {
|
||||
const body, title = "apiTestBody", "apiTestTitle"
|
||||
|
||||
repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
@@ -141,9 +149,7 @@ func TestAPICreateIssue(t *testing.T) {
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
}
|
||||
|
||||
func TestAPICreateIssueParallel(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
func testAPICreateIssueParallel(t *testing.T) {
|
||||
// FIXME: There seems to be a bug in github.com/mattn/go-sqlite3 with sqlite_unlock_notify, when doing concurrent writes to the same database,
|
||||
// some requests may get stuck in "go-sqlite3.(*SQLiteRows).Next", "go-sqlite3.(*SQLiteStmt).exec" and "go-sqlite3.unlock_notify_wait",
|
||||
// because the "unlock_notify_wait" never returns and the internal lock never gets releases.
|
||||
@@ -151,7 +157,7 @@ func TestAPICreateIssueParallel(t *testing.T) {
|
||||
// The trigger is: a previous test created issues and made the real issue indexer queue start processing, then this test does concurrent writing.
|
||||
// Adding this "Sleep" makes go-sqlite3 "finish" some internal operations before concurrent writes and then won't get stuck.
|
||||
// To reproduce: make a new test run these 2 tests enough times:
|
||||
// > func TestBug() { for i := 0; i < 100; i++ { testAPICreateIssue(t); testAPICreateIssueParallel(t) } }
|
||||
// > func testBug() { for i := 0; i < 100; i++ { testAPICreateIssue(t); testAPICreateIssueParallel(t) } }
|
||||
// Usually the test gets stuck in fewer than 10 iterations without this "sleep".
|
||||
time.Sleep(time.Second)
|
||||
|
||||
@@ -196,9 +202,7 @@ func TestAPICreateIssueParallel(t *testing.T) {
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestAPIEditIssue(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
func testAPIEditIssue(t *testing.T) {
|
||||
issueBefore := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10})
|
||||
repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID})
|
||||
@@ -262,11 +266,9 @@ func TestAPIEditIssue(t *testing.T) {
|
||||
assert.Equal(t, title, issueAfter.Title)
|
||||
}
|
||||
|
||||
func TestAPISearchIssues(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
// as this API was used in the frontend, it uses UI page size
|
||||
expectedIssueCount := min(20, setting.UI.IssuePagingNum) // 20 is from the fixtures
|
||||
func testAPISearchIssues(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.API.DefaultPagingNum, 20)()
|
||||
expectedIssueCount := 20 // 20 is from the fixtures
|
||||
|
||||
link, _ := url.Parse("/api/v1/repos/issues/search")
|
||||
token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadIssue)
|
||||
@@ -361,11 +363,37 @@ func TestAPISearchIssues(t *testing.T) {
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &apiIssues)
|
||||
assert.Len(t, apiIssues, 2)
|
||||
|
||||
query = url.Values{"created": {"1"}} // issues created by the auth user
|
||||
link.RawQuery = query.Encode()
|
||||
req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &apiIssues)
|
||||
assert.Len(t, apiIssues, 5)
|
||||
|
||||
query = url.Values{"created": {"1"}, "type": {"pulls"}} // prs created by the auth user
|
||||
link.RawQuery = query.Encode()
|
||||
req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &apiIssues)
|
||||
assert.Len(t, apiIssues, 3)
|
||||
|
||||
query = url.Values{"created_by": {"user2"}} // issues created by the user2
|
||||
link.RawQuery = query.Encode()
|
||||
req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &apiIssues)
|
||||
assert.Len(t, apiIssues, 9)
|
||||
|
||||
query = url.Values{"created_by": {"user2"}, "type": {"pulls"}} // prs created by user2
|
||||
link.RawQuery = query.Encode()
|
||||
req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &apiIssues)
|
||||
assert.Len(t, apiIssues, 3)
|
||||
}
|
||||
|
||||
func TestAPISearchIssuesWithLabels(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
func testAPISearchIssuesWithLabels(t *testing.T) {
|
||||
// as this API was used in the frontend, it uses UI page size
|
||||
expectedIssueCount := min(20, setting.UI.IssuePagingNum) // 20 is from the fixtures
|
||||
|
||||
@@ -420,3 +448,55 @@ func TestAPISearchIssuesWithLabels(t *testing.T) {
|
||||
DecodeJSON(t, resp, &apiIssues)
|
||||
assert.Len(t, apiIssues, 2)
|
||||
}
|
||||
|
||||
func testAPIIssueContentVersion(t *testing.T) {
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10})
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
|
||||
session := loginUser(t, owner.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
|
||||
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d", owner.Name, repo.Name, issue.Index)
|
||||
|
||||
t.Run("ResponseIncludesContentVersion", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
req := NewRequest(t, "GET", urlStr).AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
apiIssue := DecodeJSON(t, resp, &api.Issue{})
|
||||
assert.GreaterOrEqual(t, apiIssue.ContentVersion, 0)
|
||||
})
|
||||
|
||||
t.Run("EditWithCorrectVersion", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
req := NewRequest(t, "GET", urlStr).AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
before := DecodeJSON(t, resp, &api.Issue{})
|
||||
req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditIssueOption{
|
||||
Body: new("updated body with correct version"),
|
||||
ContentVersion: new(before.ContentVersion),
|
||||
}).AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusCreated)
|
||||
after := DecodeJSON(t, resp, &api.Issue{})
|
||||
assert.Equal(t, "updated body with correct version", after.Body)
|
||||
assert.Greater(t, after.ContentVersion, before.ContentVersion)
|
||||
})
|
||||
|
||||
t.Run("EditWithWrongVersion", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditIssueOption{
|
||||
Body: new("should fail"),
|
||||
ContentVersion: new(99999),
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusConflict)
|
||||
})
|
||||
|
||||
t.Run("EditWithoutVersion", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditIssueOption{
|
||||
Body: new("edit without version succeeds"),
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -79,6 +79,12 @@ func TestAPIDeleteTrackedTime(t *testing.T) {
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
|
||||
// Deletion should be scoped to the issue in the URL
|
||||
time5 := unittest.AssertExistsAndLoadBean(t, &issues_model.TrackedTime{ID: 5})
|
||||
req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/%d/times/%d", user2.Name, issue2.Repo.Name, issue2.Index, time5.ID).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
time3 := unittest.AssertExistsAndLoadBean(t, &issues_model.TrackedTime{ID: 3})
|
||||
req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/%d/times/%d", user2.Name, issue2.Repo.Name, issue2.Index, time3.ID).
|
||||
AddTokenAuth(token)
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/routers"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNodeinfo(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
defer test.MockVariableValue(&setting.Federation.Enabled, true)()
|
||||
defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())()
|
||||
|
||||
req := NewRequest(t, "GET", "/api/v1/nodeinfo")
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
VerifyJSONSchema(t, resp, "nodeinfo_2.1.json")
|
||||
|
||||
var nodeinfo api.NodeInfo
|
||||
DecodeJSON(t, resp, &nodeinfo)
|
||||
assert.True(t, nodeinfo.OpenRegistrations)
|
||||
assert.Equal(t, "gitea", nodeinfo.Software.Name)
|
||||
assert.Equal(t, 29, nodeinfo.Usage.Users.Total)
|
||||
assert.Equal(t, 22, nodeinfo.Usage.LocalPosts)
|
||||
assert.Equal(t, 3, nodeinfo.Usage.LocalComments)
|
||||
}
|
||||
@@ -200,7 +200,7 @@ func TestAPINotificationPUT(t *testing.T) {
|
||||
assert.False(t, apiNL[0].Pinned)
|
||||
|
||||
//
|
||||
// Now nofication ID 2 is the first in the list and is unread.
|
||||
// Now notification ID 2 is the first in the list and is unread.
|
||||
//
|
||||
req = NewRequest(t, "GET", "/api/v1/notifications?all=true").
|
||||
AddTokenAuth(token)
|
||||
@@ -212,3 +212,23 @@ func TestAPINotificationPUT(t *testing.T) {
|
||||
assert.True(t, apiNL[0].Unread)
|
||||
assert.False(t, apiNL[0].Pinned)
|
||||
}
|
||||
|
||||
func TestAPINotificationPublicOnly(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
thread5 := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{ID: 5})
|
||||
|
||||
token := getUserToken(t, user2.Name, auth_model.AccessTokenScopeReadNotification, auth_model.AccessTokenScopePublicOnly)
|
||||
req := NewRequest(t, "GET", "/api/v1/notifications").
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
|
||||
req = NewRequest(t, "GET", "/api/v1/notifications/new").
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications/threads/%d", thread5.ID)).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
}
|
||||
|
||||
@@ -137,34 +137,45 @@ func TestAPIOrgGeneral(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("OrgEdit", func(t *testing.T) {
|
||||
org := api.EditOrgOption{
|
||||
FullName: "Org3 organization new full name",
|
||||
Description: "A new description",
|
||||
Website: "https://try.gitea.io/new",
|
||||
Location: "Beijing",
|
||||
Visibility: "private",
|
||||
}
|
||||
req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org).AddTokenAuth(user1Token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"})
|
||||
assert.NotEqual(t, api.VisibleTypeLimited, org3.Visibility)
|
||||
|
||||
var apiOrg api.Organization
|
||||
DecodeJSON(t, resp, &apiOrg)
|
||||
org3Edit := api.EditOrgOption{
|
||||
FullName: new("new full name"),
|
||||
Description: new("new description"),
|
||||
Website: new("https://org3-new-website.example.com"),
|
||||
Location: new("new location"),
|
||||
Visibility: new("limited"),
|
||||
Email: new("org3-new-email@example.com"),
|
||||
}
|
||||
req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org3Edit).AddTokenAuth(user1Token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
apiOrg := DecodeJSON(t, resp, &api.Organization{})
|
||||
|
||||
assert.Equal(t, "org3", apiOrg.Name)
|
||||
assert.Equal(t, org.FullName, apiOrg.FullName)
|
||||
assert.Equal(t, org.Description, apiOrg.Description)
|
||||
assert.Equal(t, org.Website, apiOrg.Website)
|
||||
assert.Equal(t, org.Location, apiOrg.Location)
|
||||
assert.Equal(t, org.Visibility, apiOrg.Visibility)
|
||||
assert.Equal(t, *org3Edit.FullName, apiOrg.FullName)
|
||||
assert.Equal(t, *org3Edit.Description, apiOrg.Description)
|
||||
assert.Equal(t, *org3Edit.Website, apiOrg.Website)
|
||||
assert.Equal(t, *org3Edit.Location, apiOrg.Location)
|
||||
assert.Equal(t, *org3Edit.Visibility, apiOrg.Visibility)
|
||||
assert.Equal(t, *org3Edit.Email, apiOrg.Email)
|
||||
org3 = unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"})
|
||||
assert.Equal(t, api.VisibleTypeLimited, org3.Visibility)
|
||||
|
||||
// empty email can clear the email, nil fields won't change the settings
|
||||
req = NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &api.EditOrgOption{
|
||||
Email: new(""),
|
||||
}).AddTokenAuth(user1Token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
apiOrg = DecodeJSON(t, resp, &api.Organization{})
|
||||
assert.Equal(t, *org3Edit.FullName, apiOrg.FullName)
|
||||
assert.Equal(t, *org3Edit.Visibility, apiOrg.Visibility)
|
||||
assert.Empty(t, apiOrg.Email)
|
||||
})
|
||||
|
||||
t.Run("OrgEditBadVisibility", func(t *testing.T) {
|
||||
t.Run("OrgEditInvalidVisibility", func(t *testing.T) {
|
||||
org := api.EditOrgOption{
|
||||
FullName: "Org3 organization new full name",
|
||||
Description: "A new description",
|
||||
Website: "https://try.gitea.io/new",
|
||||
Location: "Beijing",
|
||||
Visibility: "badvisibility",
|
||||
Visibility: new("invalid-visibility"),
|
||||
}
|
||||
req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org).AddTokenAuth(user1Token)
|
||||
MakeRequest(t, req, http.StatusUnprocessableEntity)
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
arch_module "code.gitea.io/gitea/modules/packages/arch"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
arch_service "code.gitea.io/gitea/services/packages/arch"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
@@ -78,34 +79,6 @@ license = MIT`)
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
readIndexContent := func(r io.Reader) (map[string]string, error) {
|
||||
gzr, err := gzip.NewReader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
content := make(map[string]string)
|
||||
|
||||
tr := tar.NewReader(gzr)
|
||||
for {
|
||||
hd, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf, err := io.ReadAll(tr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
content[hd.Name] = string(buf)
|
||||
}
|
||||
|
||||
return content, nil
|
||||
}
|
||||
|
||||
compressions := []string{"gz", "xz", "zst"}
|
||||
repositories := []string{"main", "testing", "with/slash", ""}
|
||||
@@ -204,7 +177,7 @@ license = MIT`)
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s", rootURL, repository, arch_service.IndexArchiveFilename))
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
content, err := readIndexContent(resp.Body)
|
||||
content, err := test.ReadAllTarGzContent(resp.Body)
|
||||
assert.NoError(t, err)
|
||||
|
||||
desc, has := content[fmt.Sprintf("%s-%s/desc", packageName, packageVersion)]
|
||||
@@ -256,7 +229,7 @@ license = MIT`)
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s", rootURL, repository, arch_service.IndexArchiveFilename))
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
content, err := readIndexContent(resp.Body)
|
||||
content, err := test.ReadAllTarGzContent(resp.Body)
|
||||
assert.NoError(t, err)
|
||||
|
||||
desc, has := content[fmt.Sprintf("%s-%s/desc", packageName, packageVersion)]
|
||||
@@ -311,7 +284,7 @@ license = MIT`)
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename))
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
content, err := readIndexContent(resp.Body)
|
||||
content, err := test.ReadAllTarGzContent(resp.Body)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, content, 2)
|
||||
|
||||
@@ -326,7 +299,7 @@ license = MIT`)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename))
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
content, err = readIndexContent(resp.Body)
|
||||
content, err = test.ReadAllTarGzContent(resp.Body)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, content, 2)
|
||||
_, has = content["gitea-test-1.0.0/desc"]
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
composer_module "code.gitea.io/gitea/modules/packages/composer"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/routers/api/packages/composer"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
@@ -27,6 +28,8 @@ func TestPackageComposer(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
otherUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
|
||||
privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 31})
|
||||
|
||||
vendorName := "gitea"
|
||||
projectName := "composer-package"
|
||||
@@ -251,5 +254,85 @@ func TestPackageComposer(t *testing.T) {
|
||||
assert.Equal(t, repo1.HTMLURL(), pkgs[0].Source.URL)
|
||||
assert.Equal(t, "git", pkgs[0].Source.Type)
|
||||
assert.Equal(t, packageVersion, pkgs[0].Source.Reference)
|
||||
|
||||
// Private repository links remain visible to callers who can access the repository.
|
||||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||
err = packages.SetRepositoryLink(t.Context(), userPkgs[0].ID, repo2.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/p2/%s/%s.json", url, vendorName, projectName)).
|
||||
AddBasicAuth(user.Name)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
result = composer.PackageMetadataResponse{}
|
||||
DecodeJSON(t, resp, &result)
|
||||
pkgs = result.Packages[packageName]
|
||||
assert.Len(t, pkgs, 1)
|
||||
assert.Equal(t, repo2.HTMLURL(), pkgs[0].Source.URL)
|
||||
assert.Equal(t, "git", pkgs[0].Source.Type)
|
||||
assert.Equal(t, packageVersion, pkgs[0].Source.Reference)
|
||||
|
||||
// Callers without repository access still get the package metadata, but not the private source URL.
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/p2/%s/%s.json", url, vendorName, projectName)).
|
||||
AddBasicAuth(otherUser.Name)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
result = composer.PackageMetadataResponse{}
|
||||
DecodeJSON(t, resp, &result)
|
||||
pkgs = result.Packages[packageName]
|
||||
assert.Len(t, pkgs, 1)
|
||||
assert.Empty(t, pkgs[0].Source.URL)
|
||||
assert.Empty(t, pkgs[0].Source.Type)
|
||||
assert.Empty(t, pkgs[0].Source.Reference)
|
||||
})
|
||||
|
||||
t.Run("WebVisibilityBadge", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
listReq := NewRequest(t, "GET", fmt.Sprintf("/%s/-/packages", user.Name)).
|
||||
AddBasicAuth(user.Name)
|
||||
listResp := MakeRequest(t, listReq, http.StatusOK)
|
||||
listDoc := NewHTMLParser(t, listResp.Body)
|
||||
assert.Equal(t, 0, listDoc.Find(".flex-item-title .ui.basic.label").Length())
|
||||
|
||||
viewReq := NewRequest(t, "GET", fmt.Sprintf("/%s/-/packages/composer/%s/%s", user.Name, neturl.PathEscape(packageName), neturl.PathEscape(packageVersion))).
|
||||
AddBasicAuth(user.Name)
|
||||
viewResp := MakeRequest(t, viewReq, http.StatusOK)
|
||||
viewDoc := NewHTMLParser(t, viewResp.Body)
|
||||
assert.Equal(t, 0, viewDoc.Find(".issue-title-header .ui.basic.label").Length())
|
||||
|
||||
privatePackageName := privateUser.Name + "/private-composer-package"
|
||||
privatePackageVersion := "1.0.0"
|
||||
privateContent := test.WriteZipArchive(map[string]string{
|
||||
"composer.json": `{
|
||||
"name": "` + privatePackageName + `",
|
||||
"description": "Private Package",
|
||||
"type": "` + packageType + `",
|
||||
"license": "` + packageLicense + `",
|
||||
"authors": [
|
||||
{
|
||||
"name": "` + packageAuthor + `"
|
||||
}
|
||||
]
|
||||
}`,
|
||||
}).Bytes()
|
||||
privateUploadURL := fmt.Sprintf("%sapi/packages/%s/composer?version=%s", setting.AppURL, privateUser.Name, privatePackageVersion)
|
||||
|
||||
uploadReq := NewRequestWithBody(t, "PUT", privateUploadURL, bytes.NewReader(privateContent)).
|
||||
AddBasicAuth(privateUser.Name)
|
||||
MakeRequest(t, uploadReq, http.StatusCreated)
|
||||
privateSession := loginUser(t, privateUser.Name)
|
||||
|
||||
privateListReq := NewRequest(t, "GET", fmt.Sprintf("/%s/-/packages", privateUser.Name))
|
||||
privateListResp := privateSession.MakeRequest(t, privateListReq, http.StatusOK)
|
||||
privateListDoc := NewHTMLParser(t, privateListResp.Body)
|
||||
assert.Equal(t, 1, privateListDoc.Find(".flex-item-title .ui.basic.label").Length())
|
||||
assert.Equal(t, "Private", privateListDoc.Find(".flex-item-title .ui.basic.label").First().Text())
|
||||
|
||||
privateViewReq := NewRequest(t, "GET", fmt.Sprintf("/%s/-/packages/composer/%s/%s", privateUser.Name, neturl.PathEscape(privatePackageName), neturl.PathEscape(privatePackageVersion)))
|
||||
privateViewResp := privateSession.MakeRequest(t, privateViewReq, http.StatusOK)
|
||||
privateViewDoc := NewHTMLParser(t, privateViewResp.Body)
|
||||
assert.Equal(t, 1, privateViewDoc.Find(".issue-title-header .ui.basic.label").Length())
|
||||
assert.Equal(t, "Private", privateViewDoc.Find(".issue-title-header .ui.basic.label").First().Text())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -346,15 +346,16 @@ func TestPackageConan(t *testing.T) {
|
||||
pb, err := packages.GetBlobByID(t.Context(), pf.BlobID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
if pf.Name == conanfileName {
|
||||
switch pf.Name {
|
||||
case conanfileName:
|
||||
assert.True(t, pf.IsLead)
|
||||
|
||||
assert.Equal(t, int64(len(buildConanfileContent(name, version1))), pb.Size)
|
||||
} else if pf.Name == conaninfoName {
|
||||
case conaninfoName:
|
||||
assert.False(t, pf.IsLead)
|
||||
|
||||
assert.Equal(t, int64(len(contentConaninfo)), pb.Size)
|
||||
} else {
|
||||
default:
|
||||
assert.FailNow(t, "unknown file", "unknown file: %s", pf.Name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,34 +88,34 @@ func TestPackageContainer(t *testing.T) {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
defaultAuthenticateValues := []string{`Bearer realm="` + setting.AppURL + `v2/token",service="container_registry",scope="*"`}
|
||||
|
||||
wwwAuthenticateForPublic := []string{
|
||||
`Bearer realm="` + setting.AppURL + `v2/token",service="container_registry",scope="*"`,
|
||||
}
|
||||
wwwAuthenticateForRequiredSignIn := []string{
|
||||
`Bearer realm="` + setting.AppURL + `v2/token",service="container_registry",scope="*"`,
|
||||
`Basic realm="Gitea Container Registry"`,
|
||||
}
|
||||
t.Run("Anonymous", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
req := NewRequest(t, "GET", setting.AppURL+"v2")
|
||||
resp := MakeRequest(t, req, http.StatusUnauthorized)
|
||||
|
||||
assert.ElementsMatch(t, defaultAuthenticateValues, resp.Header().Values("WWW-Authenticate"))
|
||||
assert.ElementsMatch(t, wwwAuthenticateForPublic, resp.Header().Values("WWW-Authenticate"))
|
||||
|
||||
req = NewRequest(t, "GET", setting.AppURL+"v2/token")
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
tokenResponse := &TokenResponse{}
|
||||
DecodeJSON(t, resp, &tokenResponse)
|
||||
|
||||
assert.NotEmpty(t, tokenResponse.Token)
|
||||
|
||||
tokenResponse := DecodeJSON(t, resp, &TokenResponse{})
|
||||
require.NotEmpty(t, tokenResponse.Token)
|
||||
anonymousToken = "Bearer " + tokenResponse.Token
|
||||
|
||||
req = NewRequest(t, "GET", setting.AppURL+"v2").
|
||||
AddTokenAuth(anonymousToken)
|
||||
req = NewRequest(t, "GET", setting.AppURL+"v2").AddTokenAuth(anonymousToken)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)()
|
||||
|
||||
req = NewRequest(t, "GET", setting.AppURL+"v2")
|
||||
MakeRequest(t, req, http.StatusUnauthorized)
|
||||
resp = MakeRequest(t, req, http.StatusUnauthorized)
|
||||
assert.ElementsMatch(t, wwwAuthenticateForRequiredSignIn, resp.Header().Values("WWW-Authenticate"))
|
||||
|
||||
req = NewRequest(t, "GET", setting.AppURL+"v2/token")
|
||||
MakeRequest(t, req, http.StatusUnauthorized)
|
||||
@@ -132,17 +132,13 @@ func TestPackageContainer(t *testing.T) {
|
||||
|
||||
req := NewRequest(t, "GET", setting.AppURL+"v2")
|
||||
resp := MakeRequest(t, req, http.StatusUnauthorized)
|
||||
assert.ElementsMatch(t, wwwAuthenticateForPublic, resp.Header().Values("WWW-Authenticate"))
|
||||
|
||||
assert.ElementsMatch(t, defaultAuthenticateValues, resp.Header().Values("WWW-Authenticate"))
|
||||
|
||||
req = NewRequest(t, "GET", setting.AppURL+"v2/token").
|
||||
AddBasicAuth(user.Name)
|
||||
req = NewRequest(t, "GET", setting.AppURL+"v2/token").AddBasicAuth(user.Name)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
tokenResponse := &TokenResponse{}
|
||||
DecodeJSON(t, resp, &tokenResponse)
|
||||
|
||||
tokenResponse := DecodeJSON(t, resp, &TokenResponse{})
|
||||
assert.NotEmpty(t, tokenResponse.Token)
|
||||
|
||||
pkgMeta, err := package_service.ParseAuthorizationToken(tokenResponse.Token)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, user.ID, pkgMeta.UserID)
|
||||
@@ -826,7 +822,6 @@ func TestPackageContainer(t *testing.T) {
|
||||
newOwnerName := "newUsername"
|
||||
|
||||
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
"name": newOwnerName,
|
||||
"email": "user2@example.com",
|
||||
"language": "en-US",
|
||||
@@ -836,7 +831,6 @@ func TestPackageContainer(t *testing.T) {
|
||||
t.Run(fmt.Sprintf("Catalog[%s]", newOwnerName), checkCatalog(newOwnerName))
|
||||
|
||||
req = NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
"name": user.Name,
|
||||
"email": "user2@example.com",
|
||||
"language": "en-US",
|
||||
|
||||
@@ -7,7 +7,9 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
neturl "net/url"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/packages"
|
||||
@@ -140,11 +142,12 @@ func TestPackageGeneric(t *testing.T) {
|
||||
t.Run("ServeDirect", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
if setting.Packages.Storage.Type == setting.MinioStorageType {
|
||||
switch setting.Packages.Storage.Type {
|
||||
case setting.MinioStorageType:
|
||||
defer test.MockVariableValue(&setting.Packages.Storage.MinioConfig.ServeDirect, true)()
|
||||
} else if setting.Packages.Storage.Type == setting.AzureBlobStorageType {
|
||||
case setting.AzureBlobStorageType:
|
||||
defer test.MockVariableValue(&setting.Packages.Storage.AzureBlobConfig.ServeDirect, true)()
|
||||
} else {
|
||||
default:
|
||||
t.Skip("Test skipped for non-Minio-storage and non-AzureBlob-storage.")
|
||||
}
|
||||
|
||||
@@ -163,6 +166,7 @@ func TestPackageGeneric(t *testing.T) {
|
||||
|
||||
resp2, err := (&http.Client{}).Get(location)
|
||||
assert.NoError(t, err)
|
||||
defer resp2.Body.Close()
|
||||
assert.Equal(t, http.StatusOK, resp2.StatusCode, location)
|
||||
|
||||
body, err := io.ReadAll(resp2.Body)
|
||||
@@ -171,6 +175,27 @@ func TestPackageGeneric(t *testing.T) {
|
||||
|
||||
checkDownloadCount(3)
|
||||
})
|
||||
|
||||
t.Run("WebAssetUsesFilename", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeGeneric)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, pvs, 1)
|
||||
|
||||
pfs, err := packages.GetFilesByVersionID(t.Context(), pvs[0].ID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, pfs)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/-/packages/generic/%s/%s/files/%d", user.Name, neturl.PathEscape(packageName), neturl.PathEscape(packageVersion), pfs[0].ID))
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
assert.Equal(t, content, resp.Body.Bytes())
|
||||
|
||||
disposition, params, err := mime.ParseMediaType(resp.Header().Get("Content-Disposition"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "attachment", disposition)
|
||||
assert.Equal(t, pfs[0].Name, params["filename"])
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
|
||||
@@ -160,7 +160,7 @@ func TestPackageMaven(t *testing.T) {
|
||||
t.Run("UploadVerifySHA1", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
t.Run("Missmatch", func(t *testing.T) {
|
||||
t.Run("Mismatch", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
putFile(t, fmt.Sprintf("/%s/%s.sha1", packageVersion, filename), "test", http.StatusBadRequest)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
neturl "net/url"
|
||||
@@ -930,4 +931,34 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("UploadMultipartForm", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
packageContent := createPackage(packageName, packageVersion).Bytes()
|
||||
|
||||
// Simulate dotnet nuget push which sends multipart/form-data with X-NuGet-ApiKey auth
|
||||
var body bytes.Buffer
|
||||
mpw := multipart.NewWriter(&body)
|
||||
part, err := mpw.CreateFormFile("package", "package.nupkg")
|
||||
assert.NoError(t, err)
|
||||
_, err = part.Write(packageContent)
|
||||
assert.NoError(t, err)
|
||||
err = mpw.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
req := NewRequestWithBody(t, "PUT", url, &body).
|
||||
SetHeader("Content-Type", mpw.FormDataContentType())
|
||||
addNuGetAPIKeyHeader(req, writeToken)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeNuGet)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, pvs, 1)
|
||||
|
||||
// Clean up
|
||||
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, packageName, packageVersion)).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -12,15 +12,16 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/packages"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
rpm_module "code.gitea.io/gitea/modules/packages/rpm"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
@@ -31,14 +32,24 @@ import (
|
||||
|
||||
func TestPackageRpm(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
|
||||
packageName := "gitea-test"
|
||||
packageVersion := "1.0.2-1"
|
||||
packageArchitecture := "x86_64"
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
|
||||
base64RpmPackageContent := `H4sICFayB2QCAGdpdGVhLXRlc3QtMS4wLjItMS14ODZfNjQucnBtAO2YV4gTQRjHJzl7wbNhhxVF
|
||||
// To build an RPM package, it needs tools lik "cpio", it's not easy to do in Golang.
|
||||
// So here we only use pre-built package contents. And to save space, the content is gzipped and base64 encoded.
|
||||
rpmContentFromGzipBase64 := func(t testing.TB, s string) []byte {
|
||||
b, err := base64.StdEncoding.DecodeString(s)
|
||||
require.NoError(t, err)
|
||||
zr, err := gzip.NewReader(bytes.NewReader(b))
|
||||
require.NoError(t, err)
|
||||
content, err := io.ReadAll(zr)
|
||||
require.NoError(t, err)
|
||||
return content
|
||||
}
|
||||
packageRpmGzipBase64 := `H4sICFayB2QCAGdpdGVhLXRlc3QtMS4wLjItMS14ODZfNjQucnBtAO2YV4gTQRjHJzl7wbNhhxVF
|
||||
VNwk2zd2PdvZ9Sxnd3Z3NllNsmF3o6congVFsWFHRWwIImIXfRER0QcRfPBJEXvvBQvWSfZTT0VQ
|
||||
8TF/MuU33zcz3+zOJGEe73lyuQBRBWKWRzDrEddjuVAkxLMc+lsFUOWfm5bvvReAalWECg/TsivU
|
||||
dyKa0U61aVnl6wj0Uxe4nc8F92hZiaYE8CO/P0r7/Quegr0c7M/AvoCaGZEIWNGUqMHrhhGROIUT
|
||||
@@ -66,14 +77,17 @@ Mu0UFYgZ/bYnuvn/vz4wtCz8qMwsHUvP0PX3tbYFUctAPdrY6tiiDtcCddDECahx7SuVNP5dpmb5
|
||||
9tMDyaXb7OAlk5acuPn57ss9mw6Wym0m1Fq2cej7tUt2LL4/b8enXU2fndk+fvv57ndnt55/cQob
|
||||
7tpp/pEjDS7cGPZ6BY430+7danDq6f42Nw49b9F7zp6BiKpJb9s5P0AYN2+L159cnrur636rx+v1
|
||||
7ae1K28QbMMcqI8CqwIrgwg9nTOp8Oj9q81plUY7ZuwXN8Vvs8wbAAA=`
|
||||
rpmPackageContent, err := base64.StdEncoding.DecodeString(base64RpmPackageContent)
|
||||
assert.NoError(t, err)
|
||||
|
||||
zr, err := gzip.NewReader(bytes.NewReader(rpmPackageContent))
|
||||
assert.NoError(t, err)
|
||||
packageRpmContent := rpmContentFromGzipBase64(t, packageRpmGzipBase64)
|
||||
|
||||
content, err := io.ReadAll(zr)
|
||||
assert.NoError(t, err)
|
||||
decodeGzipXML := func(t testing.TB, resp *httptest.ResponseRecorder, v any) {
|
||||
t.Helper()
|
||||
|
||||
zr, err := gzip.NewReader(resp.Body)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, xml.NewDecoder(zr).Decode(v))
|
||||
}
|
||||
|
||||
rootURL := fmt.Sprintf("/api/packages/%s/rpm", user.Name)
|
||||
|
||||
@@ -99,7 +113,7 @@ gpgcheck=1
|
||||
gpgkey=%sapi/packages/%s/rpm/repository.key`,
|
||||
strings.Join(append([]string{user.LowerName}, groupParts...), "-"),
|
||||
strings.Join(append([]string{user.Name, setting.AppName}, groupParts...), " - "),
|
||||
util.URLJoin(setting.AppURL, groupURL),
|
||||
strings.TrimSuffix(setting.AppURL, "/")+groupURL,
|
||||
setting.AppURL,
|
||||
user.Name,
|
||||
)
|
||||
@@ -120,10 +134,10 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`,
|
||||
t.Run("Upload", func(t *testing.T) {
|
||||
url := groupURL + "/upload"
|
||||
|
||||
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content))
|
||||
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(packageRpmContent))
|
||||
MakeRequest(t, req, http.StatusUnauthorized)
|
||||
|
||||
req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
|
||||
req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(packageRpmContent)).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
@@ -146,9 +160,9 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`,
|
||||
|
||||
pb, err := packages.GetBlobByID(t.Context(), pfs[0].BlobID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(len(content)), pb.Size)
|
||||
assert.Equal(t, int64(len(packageRpmContent)), pb.Size)
|
||||
|
||||
req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
|
||||
req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(packageRpmContent)).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusConflict)
|
||||
})
|
||||
@@ -159,12 +173,12 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`,
|
||||
// download the package without the file name
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture))
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
assert.Equal(t, content, resp.Body.Bytes())
|
||||
assert.Equal(t, packageRpmContent, resp.Body.Bytes())
|
||||
|
||||
// download the package with a file name (it can be anything)
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s/any-file-name", groupURL, packageName, packageVersion, packageArchitecture))
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
assert.Equal(t, content, resp.Body.Bytes())
|
||||
assert.Equal(t, packageRpmContent, resp.Body.Bytes())
|
||||
})
|
||||
|
||||
t.Run("Repository", func(t *testing.T) {
|
||||
@@ -248,15 +262,6 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`,
|
||||
assert.Contains(t, resp.Body.String(), "-----BEGIN PGP SIGNATURE-----")
|
||||
})
|
||||
|
||||
decodeGzipXML := func(t testing.TB, resp *httptest.ResponseRecorder, v any) {
|
||||
t.Helper()
|
||||
|
||||
zr, err := gzip.NewReader(resp.Body)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, xml.NewDecoder(zr).Decode(v))
|
||||
}
|
||||
|
||||
t.Run("primary.xml.gz", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
@@ -331,7 +336,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`,
|
||||
assert.Equal(t, "sha256", p.Checksum.Type)
|
||||
assert.Equal(t, "f1d5d2ffcbe4a7568e98b864f40d923ecca084e9b9bcd5977ed6521c46d3fa4c", p.Checksum.Checksum)
|
||||
assert.Equal(t, "https://gitea.io", p.URL)
|
||||
assert.EqualValues(t, len(content), p.Size.Package)
|
||||
assert.EqualValues(t, len(packageRpmContent), p.Size.Package)
|
||||
assert.EqualValues(t, 13, p.Size.Installed)
|
||||
assert.EqualValues(t, 272, p.Size.Archive)
|
||||
assert.Equal(t, fmt.Sprintf("package/%s/%s/%s/%s", packageName, packageVersion, packageArchitecture, fmt.Sprintf("%s-%s.%s.rpm", packageName, packageVersion, packageArchitecture)), p.Location.Href)
|
||||
@@ -421,6 +426,417 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Errata", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
type updateInfo struct {
|
||||
XMLName xml.Name `xml:"updates"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Updates []*rpm_module.Update `xml:"update"`
|
||||
}
|
||||
|
||||
errataURL := fmt.Sprintf("%s/package/%s/%s/errata", groupURL, packageName, packageVersion)
|
||||
|
||||
advisory := rpm_module.Update{
|
||||
From: "security@example.com",
|
||||
Status: "stable",
|
||||
Type: "security",
|
||||
Version: "1.0",
|
||||
ID: "CVE-2023-1234",
|
||||
Title: "Test Security Update",
|
||||
Severity: "Important",
|
||||
Description: "This is a test security update.",
|
||||
References: []*rpm_module.Reference{
|
||||
{
|
||||
Href: "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-1234",
|
||||
ID: "CVE-2023-1234",
|
||||
Title: "CVE-2023-1234",
|
||||
Type: "cve",
|
||||
},
|
||||
},
|
||||
PkgList: []*rpm_module.Collection{
|
||||
{
|
||||
Short: "el9",
|
||||
Packages: []*rpm_module.UpdatePackage{
|
||||
{
|
||||
Arch: packageArchitecture,
|
||||
Name: packageName,
|
||||
Release: "1",
|
||||
Src: "gitea-test-1.0.2-1.src.rpm",
|
||||
Version: "1.0.2",
|
||||
Filename: fmt.Sprintf("%s-%s.%s.rpm", packageName, packageVersion, packageArchitecture),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
updates := []*rpm_module.Update{&advisory}
|
||||
body, err := json.Marshal(updates)
|
||||
assert.NoError(t, err)
|
||||
|
||||
req := NewRequestWithBody(t, "POST", errataURL, bytes.NewReader(body)).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
url := groupURL + "/repodata"
|
||||
|
||||
// Check repomd.xml contains updateinfo
|
||||
req = NewRequest(t, "GET", url+"/repomd.xml")
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
type testRepoData struct {
|
||||
Type string `xml:"type,attr"`
|
||||
}
|
||||
var repomd struct {
|
||||
Data []*testRepoData `xml:"data"`
|
||||
}
|
||||
err = xml.NewDecoder(resp.Body).Decode(&repomd)
|
||||
require.NoError(t, err)
|
||||
|
||||
found := slices.IndexFunc(repomd.Data, func(s *testRepoData) bool {
|
||||
return s.Type == "updateinfo"
|
||||
}) >= 0
|
||||
assert.True(t, found, "updateinfo not found in repomd.xml")
|
||||
|
||||
// Now check updateinfo.xml.gz
|
||||
req = NewRequest(t, "GET", url+"/updateinfo.xml.gz")
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var result updateInfo
|
||||
decodeGzipXML(t, resp, &result)
|
||||
|
||||
assert.Equal(t, "http://linux.duke.edu/metadata/updateinfo", result.Xmlns)
|
||||
assert.Len(t, result.Updates, 1)
|
||||
assert.Equal(t, "CVE-2023-1234", result.Updates[0].ID)
|
||||
assert.NotEmpty(t, result.Updates[0].Issued.Date)
|
||||
assert.NotEmpty(t, result.Updates[0].Updated.Date)
|
||||
})
|
||||
|
||||
t.Run("InvalidJSON", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
req := NewRequestWithBody(t, "POST", errataURL, strings.NewReader("invalid json")).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("NullElementsInJSON", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
// Send a payload with null inside arrays
|
||||
payload := `[
|
||||
{
|
||||
"id": "CVE-2023-5678",
|
||||
"from": "security@example.com",
|
||||
"status": "stable",
|
||||
"type": "security",
|
||||
"version": "1.0",
|
||||
"title": "Test Null Elements",
|
||||
"severity": "Important",
|
||||
"description": "Test null elements",
|
||||
"pkg_list": [
|
||||
null,
|
||||
{
|
||||
"short": "el9",
|
||||
"packages": [
|
||||
null,
|
||||
{
|
||||
"arch": "x86_64",
|
||||
"name": "gitea",
|
||||
"release": "1",
|
||||
"src": "gitea-1.0.0-1.src.rpm",
|
||||
"version": "1.0.0",
|
||||
"filename": "gitea-1.0.0-1.x86_64.rpm"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]`
|
||||
|
||||
req := NewRequestWithBody(t, "POST", errataURL, strings.NewReader(payload)).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// Verify it was stored correctly (skipping nulls)
|
||||
url := groupURL + "/repodata"
|
||||
req = NewRequest(t, "GET", url+"/updateinfo.xml.gz")
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var result updateInfo
|
||||
decodeGzipXML(t, resp, &result)
|
||||
|
||||
// We need to find the new advisory CVE-2023-5678
|
||||
var newAdvisory *rpm_module.Update
|
||||
for _, u := range result.Updates {
|
||||
if u.ID == "CVE-2023-5678" {
|
||||
newAdvisory = u
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.NotNil(t, newAdvisory)
|
||||
assert.Len(t, newAdvisory.PkgList, 1)
|
||||
assert.Equal(t, "el9", newAdvisory.PkgList[0].Short)
|
||||
assert.Len(t, newAdvisory.PkgList[0].Packages, 1)
|
||||
assert.Equal(t, "gitea", newAdvisory.PkgList[0].Packages[0].Name)
|
||||
})
|
||||
|
||||
t.Run("PackageNotFound", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
badURL := fmt.Sprintf("%s/package/%s/non-existent-version/errata", groupURL, packageName)
|
||||
updates := []*rpm_module.Update{&advisory}
|
||||
body, err := json.Marshal(updates)
|
||||
assert.NoError(t, err)
|
||||
|
||||
req := NewRequestWithBody(t, "POST", badURL, bytes.NewReader(body)).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("MergeAdvisories", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
// Upload a second advisory with the same ID but a different package
|
||||
advisory2 := advisory
|
||||
advisory2.PkgList = []*rpm_module.Collection{
|
||||
{
|
||||
Short: "el9",
|
||||
Packages: []*rpm_module.UpdatePackage{
|
||||
{
|
||||
Arch: packageArchitecture,
|
||||
Name: "another-package",
|
||||
Release: "1",
|
||||
Src: "another-package-1.0.0-1.src.rpm",
|
||||
Version: "1.0.0",
|
||||
Filename: "another-package-1.0.0-1.x86_64.rpm",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
updates := []*rpm_module.Update{&advisory2}
|
||||
body, err := json.Marshal(updates)
|
||||
assert.NoError(t, err)
|
||||
|
||||
req := NewRequestWithBody(t, "POST", errataURL, bytes.NewReader(body)).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// Check updateinfo.xml.gz again
|
||||
url := groupURL + "/repodata"
|
||||
req = NewRequest(t, "GET", url+"/updateinfo.xml.gz")
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var result updateInfo
|
||||
decodeGzipXML(t, resp, &result)
|
||||
|
||||
var targetUpdate *rpm_module.Update
|
||||
for _, u := range result.Updates {
|
||||
if u.ID == "CVE-2023-1234" {
|
||||
targetUpdate = u
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.NotNil(t, targetUpdate)
|
||||
// Verify that package lists are merged into the same collection
|
||||
assert.Len(t, targetUpdate.PkgList, 1)
|
||||
assert.Len(t, targetUpdate.PkgList[0].Packages, 2)
|
||||
assert.Equal(t, "another-package", result.Updates[0].PkgList[0].Packages[1].Name)
|
||||
})
|
||||
|
||||
t.Run("NewCollection", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
// Upload a third advisory with the same ID but a different collection
|
||||
advisory3 := advisory
|
||||
advisory3.PkgList = []*rpm_module.Collection{
|
||||
{
|
||||
Short: "el8",
|
||||
Packages: []*rpm_module.UpdatePackage{
|
||||
{
|
||||
Arch: packageArchitecture,
|
||||
Name: packageName,
|
||||
Release: "1",
|
||||
Src: "gitea-test-1.0.2-1.src.rpm",
|
||||
Version: "1.0.2",
|
||||
Filename: fmt.Sprintf("%s-%s.%s.rpm", packageName, packageVersion, packageArchitecture),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
updates := []*rpm_module.Update{&advisory3}
|
||||
body, err := json.Marshal(updates)
|
||||
assert.NoError(t, err)
|
||||
|
||||
req := NewRequestWithBody(t, "POST", errataURL, bytes.NewReader(body)).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// Check updateinfo.xml.gz again
|
||||
url := groupURL + "/repodata"
|
||||
req = NewRequest(t, "GET", url+"/updateinfo.xml.gz")
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var result updateInfo
|
||||
decodeGzipXML(t, resp, &result)
|
||||
|
||||
var targetUpdate *rpm_module.Update
|
||||
for _, u := range result.Updates {
|
||||
if u.ID == "CVE-2023-1234" {
|
||||
targetUpdate = u
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.NotNil(t, targetUpdate)
|
||||
// Verify that we now have 2 collections
|
||||
assert.Len(t, targetUpdate.PkgList, 2)
|
||||
// We need to be careful with order, map iteration is random
|
||||
// Let's check both exist
|
||||
shorts := []string{targetUpdate.PkgList[0].Short, targetUpdate.PkgList[1].Short}
|
||||
assert.Contains(t, shorts, "el9")
|
||||
assert.Contains(t, shorts, "el8")
|
||||
})
|
||||
|
||||
t.Run("Idempotency", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
updates := []*rpm_module.Update{&advisory}
|
||||
body, err := json.Marshal(updates)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Post twice
|
||||
req := NewRequestWithBody(t, "POST", errataURL, bytes.NewReader(body)).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
req = NewRequestWithBody(t, "POST", errataURL, bytes.NewReader(body)).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// Check updateinfo.xml.gz
|
||||
url := groupURL + "/repodata"
|
||||
req = NewRequest(t, "GET", url+"/updateinfo.xml.gz")
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var result updateInfo
|
||||
decodeGzipXML(t, resp, &result)
|
||||
|
||||
var targetUpdate *rpm_module.Update
|
||||
for _, u := range result.Updates {
|
||||
if u.ID == "CVE-2023-1234" {
|
||||
targetUpdate = u
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.NotNil(t, targetUpdate)
|
||||
assert.Len(t, targetUpdate.PkgList, 2)
|
||||
|
||||
var el9Coll *rpm_module.Collection
|
||||
for _, coll := range targetUpdate.PkgList {
|
||||
if coll.Short == "el9" {
|
||||
el9Coll = coll
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.NotNil(t, el9Coll)
|
||||
assert.Len(t, el9Coll.Packages, 2)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("NoArch", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
noarchPackageName := "gitea-noarch-test"
|
||||
noarchPackageVersion := "1.0.0-1"
|
||||
noarchPackageGzipBase64 := `H4sICKC05mkAA2dpdGVhLW5vYXJjaC10ZXN0LTEuMC4wLTEubm9hcmNoLnJwbQDtmLtrFEEcx+dy` +
|
||||
`p0ZROYliFMUTLJJiz53HPgbBBwZR0Jgiglo5sztzLt6Lu0uIYmEhFhFUsBUJahd8gHZWKvgnpLES` +
|
||||
`VIwYo43a6Dm7+zvfhbXsF+ZmP/N7zM5y03wXZt89yyOjbiXqKGHVG6IVnLQ6qt2xcNku2xZG/6wc` +
|
||||
`WvL70qXbr3PwuAyh4gMz74TnW2YumqJVZl76vQPKrQEeTjn/2swFM6rAb9N61Ezr84sQPwfx9xA/` +
|
||||
`b8Il6nEpWSgwVz7lrqOZ9oWjqKZSShIQT/k4cHWIAtcJHNsR1OGKSp8w5QcMM+4SiSV1OSY293wi` +
|
||||
`TAMmmBauL1XoeJ7mXAqhafL6BXzzSd+N2eFP9x8/nHt25u7CBbN49t8/YKZMmTJlypQpU6ZMmTJl` +
|
||||
`ypTpP1biiXS73Sso8TR+8U1KCOU+mHkXSnyN3HPICc3oh5yeTxL7Jn3A88Brgd8Ab0Q/fJTlZmwC` +
|
||||
`XgC2gd+h1FfZDbwI9SPAHyB+EPgjxMeAP0O/ceAvED8B/BVYp1xYC1wDXg/nuww8CPvFvlHePG6A` +
|
||||
`+D3gjcBd4KG0X24d1B9N63Nw3sKxND9XApaQPwQcAm8HVsAMWANz4CrwjpQHrsJ+8D0GnkIcvsfA` +
|
||||
`C9j/OPBLyL8GPA/xmZj3oj/8OZT4cwij0WStFK+VmiI4JSoKjf8EZdMgevVgRjbqqg1/mEMHxtGR` +
|
||||
`erupgkhHKkTVqD4xhdLuf27VswLL7VZQbjVrf3mZ9KVX9IZJqkZyaG+j1mypdluF+6KqGhU11R5G` +
|
||||
`EItXRqKKKf6xNiZOVxsiSW7vF5NqrKV0NDWMqNmfWRixsptYkgx+iV1O/Mn+nldpHSYlq4KCZtRA` +
|
||||
`lTNRE3E4lDWp6mGjZaUHTWomOtrykdCUKxxgLjTzfKG0J2wqFfeVFjyMzT1CfC255lRRn5HQDT2J` +
|
||||
`mcDMdQRzeNqL0NBmhEimlTDpnoeV7xGPYSmpZ3vcljQIbYf6SmjJXEwoVT6xpc+k4wkS/7fSC97t` +
|
||||
`xvcCFbdcTO92X57apoHNSquLs6s3b3s0uHXPpes77+yZnp5eaa7m3PxbJ3YYvwFme3wbyRUAAA==`
|
||||
noarchRpmPackageContent := rpmContentFromGzipBase64(t, noarchPackageGzipBase64)
|
||||
// Upload the noarch RPM
|
||||
req := NewRequestWithBody(t, "PUT", groupURL+"/upload", bytes.NewReader(noarchRpmPackageContent)).AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
// The noarch package should appear in primary.xml when any arch is requested.
|
||||
// At this point the group contains the existing x86_64 package + this noarch one.
|
||||
req = NewRequest(t, "GET", groupURL+"/repodata/primary.xml.gz")
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
type PackageSummary struct {
|
||||
Name string `xml:"name"`
|
||||
Architecture string `xml:"arch"`
|
||||
}
|
||||
type PrimaryMetadata struct {
|
||||
XMLName xml.Name `xml:"metadata"`
|
||||
PackageCount int `xml:"packages,attr"`
|
||||
Packages []PackageSummary `xml:"package"`
|
||||
}
|
||||
|
||||
var primary PrimaryMetadata
|
||||
decodeGzipXML(t, resp, &primary)
|
||||
|
||||
// Both the arch-specific package uploaded earlier and the noarch one must be present.
|
||||
assert.Equal(t, 2, primary.PackageCount)
|
||||
assert.Len(t, primary.Packages, 2)
|
||||
|
||||
archNames := make([]string, 0, len(primary.Packages))
|
||||
for _, p := range primary.Packages {
|
||||
archNames = append(archNames, p.Architecture)
|
||||
}
|
||||
assert.Contains(t, archNames, packageArchitecture) // x86_64 from the Upload subtest
|
||||
assert.Contains(t, archNames, "noarch")
|
||||
|
||||
// noarch package must be downloadable regardless of the arch path used.
|
||||
for _, arch := range []string{"noarch", "x86_64", "aarch64", "my_arch"} {
|
||||
req = NewRequest(t, "GET", fmt.Sprintf(
|
||||
"%s/package/%s/%s/%s",
|
||||
groupURL, noarchPackageName, noarchPackageVersion, arch,
|
||||
))
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
}
|
||||
|
||||
// Clean up: delete via the canonical noarch path.
|
||||
req = NewRequest(t, "DELETE", fmt.Sprintf(
|
||||
"%s/package/%s/%s/noarch",
|
||||
groupURL, noarchPackageName, noarchPackageVersion,
|
||||
)).AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// After deletion, the noarch package must no longer be reachable via any arch.
|
||||
for _, arch := range []string{"noarch", "x86_64", "aarch64"} {
|
||||
req = NewRequest(t, "GET", fmt.Sprintf(
|
||||
"%s/package/%s/%s/%s",
|
||||
groupURL, noarchPackageName, noarchPackageVersion, arch,
|
||||
))
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
}
|
||||
|
||||
// The x86_64 package from the Upload subtest must still be present.
|
||||
req = NewRequest(t, "GET", groupURL+"/repodata/primary.xml.gz")
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var primaryAfter PrimaryMetadata
|
||||
decodeGzipXML(t, resp, &primaryAfter)
|
||||
assert.Equal(t, 1, primaryAfter.PackageCount)
|
||||
assert.Equal(t, packageArchitecture, primaryAfter.Packages[0].Architecture)
|
||||
})
|
||||
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
@@ -442,7 +858,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`,
|
||||
t.Run("UploadSign", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
url := groupURL + "/upload?sign=true"
|
||||
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
|
||||
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(packageRpmContent)).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
swift_module "code.gitea.io/gitea/modules/packages/swift"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
swift_router "code.gitea.io/gitea/routers/api/packages/swift"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
@@ -35,9 +36,26 @@ func TestPackageSwift(t *testing.T) {
|
||||
packageID := packageScope + "." + packageName
|
||||
packageVersion := "1.0.3"
|
||||
packageVersion2 := "1.0.4"
|
||||
packageVersion3 := "1.0.5"
|
||||
packageAuthor := "KN4CK3R"
|
||||
packageDescription := "Gitea Test Package"
|
||||
packageRepositoryURL := "https://gitea.io/gitea/gitea"
|
||||
packageCodeRepositoryURL := "https://gitea.io/gitea/gitea" // this one is not used as a property, it is meta
|
||||
packageLicenseURL := "https://opensource.org/license/mit"
|
||||
packageRepositoryURL1 := "https://gitea.io/gitea/repo"
|
||||
packageRepositoryURLs := []string{packageRepositoryURL1, "https://gitea.io/gitea/repo.git", "ssh://git@gitea.io/gitea/repo.git"}
|
||||
makePackageMetadataJSON := func(ver string) string {
|
||||
tmpl := `{
|
||||
"name":"` + packageName + `",
|
||||
"version":"%s",
|
||||
"description":"` + packageDescription + `",
|
||||
"codeRepository":"` + packageCodeRepositoryURL + `",
|
||||
"licenseURL":"` + packageLicenseURL + `",
|
||||
"author":{"givenName":"` + packageAuthor + `"},
|
||||
"repositoryURLs":["` + strings.Join(packageRepositoryURLs, `","`) + `"]
|
||||
}`
|
||||
return fmt.Sprintf(tmpl, ver)
|
||||
}
|
||||
|
||||
contentManifest1 := "// swift-tools-version:5.7\n//\n// Package.swift"
|
||||
contentManifest2 := "// swift-tools-version:5.6\n//\n// Package@swift-5.6.swift"
|
||||
|
||||
@@ -146,7 +164,7 @@ func TestPackageSwift(t *testing.T) {
|
||||
"Package.swift": contentManifest1,
|
||||
"Package@swift-5.6.swift": contentManifest2,
|
||||
}),
|
||||
`{"name":"`+packageName+`","version":"`+packageVersion+`","description":"`+packageDescription+`","codeRepository":"`+packageRepositoryURL+`","author":{"givenName":"`+packageAuthor+`"},"repositoryURLs":["`+packageRepositoryURL+`"]}`,
|
||||
makePackageMetadataJSON(packageVersion),
|
||||
)
|
||||
|
||||
pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeSwift)
|
||||
@@ -164,8 +182,8 @@ func TestPackageSwift(t *testing.T) {
|
||||
assert.Len(t, metadata.Manifests, 2)
|
||||
assert.Equal(t, contentManifest1, metadata.Manifests[""].Content)
|
||||
assert.Equal(t, contentManifest2, metadata.Manifests["5.6"].Content)
|
||||
assert.Len(t, pd.VersionProperties, 1)
|
||||
assert.Equal(t, packageRepositoryURL, pd.VersionProperties.GetByName(swift_module.PropertyRepositoryURL))
|
||||
assert.Len(t, pd.VersionProperties, 3)
|
||||
assert.Equal(t, packageRepositoryURL1, pd.VersionProperties.GetByName(swift_module.PropertyRepositoryURL))
|
||||
|
||||
pfs, err := packages.GetFilesByVersionID(t.Context(), pvs[0].ID)
|
||||
assert.NoError(t, err)
|
||||
@@ -234,7 +252,7 @@ func TestPackageSwift(t *testing.T) {
|
||||
"Package.swift": contentManifest1,
|
||||
"Package@swift-5.6.swift": contentManifest2,
|
||||
}),
|
||||
`{"name":"`+packageName+`","version":"`+packageVersion2+`","description":"`+packageDescription+`","codeRepository":"`+packageRepositoryURL+`","author":{"givenName":"`+packageAuthor+`"},"repositoryURLs":["`+packageRepositoryURL+`"]}`,
|
||||
makePackageMetadataJSON(packageVersion2),
|
||||
)
|
||||
|
||||
pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeSwift)
|
||||
@@ -252,8 +270,8 @@ func TestPackageSwift(t *testing.T) {
|
||||
assert.Len(t, metadata.Manifests, 2)
|
||||
assert.Equal(t, contentManifest1, metadata.Manifests[""].Content)
|
||||
assert.Equal(t, contentManifest2, metadata.Manifests["5.6"].Content)
|
||||
assert.Len(t, pd.VersionProperties, 1)
|
||||
assert.Equal(t, packageRepositoryURL, pd.VersionProperties.GetByName(swift_module.PropertyRepositoryURL))
|
||||
assert.Len(t, pd.VersionProperties, 3)
|
||||
assert.Equal(t, packageRepositoryURL1, pd.VersionProperties.GetByName(swift_module.PropertyRepositoryURL))
|
||||
|
||||
pfs, err := packages.GetFilesByVersionID(t.Context(), thisPackageVersion.ID)
|
||||
assert.NoError(t, err)
|
||||
@@ -318,7 +336,7 @@ func TestPackageSwift(t *testing.T) {
|
||||
AddBasicAuth(user.Name)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
assert.Equal(t, body, resp.Body.String())
|
||||
assert.JSONEq(t, body, resp.Body.String())
|
||||
})
|
||||
|
||||
t.Run("PackageVersionMetadata", func(t *testing.T) {
|
||||
@@ -354,8 +372,11 @@ func TestPackageSwift(t *testing.T) {
|
||||
assert.Equal(t, packageVersion, result.Metadata.Version)
|
||||
assert.Equal(t, packageDescription, result.Metadata.Description)
|
||||
assert.Equal(t, "Swift", result.Metadata.ProgrammingLanguage.Name)
|
||||
assert.Equal(t, packageLicenseURL, result.Metadata.LicenseURL)
|
||||
require.NotNil(t, result.Metadata.Author)
|
||||
assert.Equal(t, packageAuthor, result.Metadata.Author.Name)
|
||||
assert.Equal(t, packageAuthor, result.Metadata.Author.GivenName)
|
||||
assert.ElementsMatch(t, packageRepositoryURLs, result.Metadata.RepositoryURLs)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/%s.json", url, packageScope, packageName, packageVersion)).
|
||||
AddBasicAuth(user.Name)
|
||||
@@ -364,6 +385,41 @@ func TestPackageSwift(t *testing.T) {
|
||||
assert.Equal(t, body, resp.Body.String())
|
||||
})
|
||||
|
||||
t.Run("UploadEmptyJSONMetadata", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
uploadURL := fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, packageVersion3)
|
||||
var body bytes.Buffer
|
||||
mpw := multipart.NewWriter(&body)
|
||||
|
||||
part, err := mpw.CreateFormFile("source-archive", "source-archive.zip")
|
||||
require.NoError(t, err)
|
||||
_, err = io.Copy(part, test.WriteZipArchive(map[string]string{
|
||||
"Package.swift": contentManifest1,
|
||||
"Package@swift-5.6.swift": contentManifest2,
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, mpw.WriteField("metadata", "{}"))
|
||||
require.NoError(t, mpw.Close())
|
||||
|
||||
req := NewRequestWithBody(t, "PUT", uploadURL, &body).
|
||||
SetHeader("Content-Type", mpw.FormDataContentType()).
|
||||
SetHeader("Accept", swift_router.AcceptJSON).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, packageVersion3)).
|
||||
AddBasicAuth(user.Name).
|
||||
SetHeader("Accept", swift_router.AcceptJSON)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
result := DecodeJSON(t, resp, &swift_router.PackageVersionMetadataResponse{})
|
||||
|
||||
assert.Nil(t, result.Metadata.Author)
|
||||
assert.Empty(t, result.Metadata.RepositoryURLs)
|
||||
assert.Empty(t, result.Metadata.CodeRepository)
|
||||
assert.Empty(t, result.Metadata.LicenseURL)
|
||||
})
|
||||
|
||||
t.Run("DownloadManifest", func(t *testing.T) {
|
||||
manifestURL := fmt.Sprintf("%s/%s/%s/%s/Package.swift", url, packageScope, packageName, packageVersion)
|
||||
|
||||
@@ -421,7 +477,7 @@ func TestPackageSwift(t *testing.T) {
|
||||
req = NewRequest(t, "GET", url+"/identifiers?url=https://unknown.host/")
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
req = NewRequest(t, "GET", url+"/identifiers?url="+packageRepositoryURL).
|
||||
req = NewRequest(t, "GET", url+"/identifiers?url="+packageRepositoryURL1).
|
||||
SetHeader("Accept", swift_router.AcceptJSON)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -85,6 +85,50 @@ func TestPackageAPI(t *testing.T) {
|
||||
assert.Equal(t, user.Name, p.Creator.UserName)
|
||||
})
|
||||
|
||||
t.Run("DeleteEntirePackage", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
packageName := "test-package-entire-delete"
|
||||
for _, version := range []string{"1.0.1", "1.0.2"} {
|
||||
url := fmt.Sprintf("/api/packages/%s/generic/%s/%s/file.bin", user.Name, packageName, version)
|
||||
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{1})).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
}
|
||||
|
||||
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/packages/%s/generic/%s", user.Name, packageName)).
|
||||
AddTokenAuth(tokenWritePackage)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s", user.Name, packageName)).
|
||||
AddTokenAuth(tokenReadPackage)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("DeletePackageVersion", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
packageName := "test-package-version-delete"
|
||||
for _, version := range []string{"1.0.1", "1.0.2"} {
|
||||
url := fmt.Sprintf("/api/packages/%s/generic/%s/%s/file.bin", user.Name, packageName, version)
|
||||
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{1})).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
}
|
||||
|
||||
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/packages/%s/generic/%s/1.0.1", user.Name, packageName)).
|
||||
AddTokenAuth(tokenWritePackage)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/1.0.1", user.Name, packageName)).
|
||||
AddTokenAuth(tokenReadPackage)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/1.0.2", user.Name, packageName)).
|
||||
AddTokenAuth(tokenReadPackage)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("ListPackageVersions", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ func TestAPIPrivateServ(t *testing.T) {
|
||||
assert.Empty(t, results)
|
||||
|
||||
// Add reading deploy key
|
||||
deployKey, err := asymkey_model.AddDeployKey(ctx, 19, "test-deploy", "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGXEEzWmm1dxb+57RoK5KVCL0w2eNv9cqJX2AGGVlkFsVDhOXHzsadS3LTK4VlEbbrDMJdoti9yM8vclA8IeRacAAAAEc3NoOg== nocomment", true)
|
||||
deployKey, err := asymkey_model.AddDeployKey(ctx, 19 /* repo id */, "test-deploy", "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGXEEzWmm1dxb+57RoK5KVCL0w2eNv9cqJX2AGGVlkFsVDhOXHzsadS3LTK4VlEbbrDMJdoti9yM8vclA8IeRacAAAAEc3NoOg== nocomment", true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Can pull from repo we're a deploy key for
|
||||
@@ -106,17 +106,17 @@ func TestAPIPrivateServ(t *testing.T) {
|
||||
assert.Empty(t, results)
|
||||
|
||||
// Cannot pull from a private repo we're not associated with
|
||||
results, extra = private.ServCommand(ctx, deployKey.ID, "user15", "big_test_private_2", perm.AccessModeRead, "git-upload-pack", "")
|
||||
results, extra = private.ServCommand(ctx, deployKey.KeyID, "user15", "big_test_private_2", perm.AccessModeRead, "git-upload-pack", "")
|
||||
assert.Error(t, extra.Error)
|
||||
assert.Empty(t, results)
|
||||
|
||||
// Cannot pull from a public repo we're not associated with
|
||||
results, extra = private.ServCommand(ctx, deployKey.ID, "user15", "big_test_public_1", perm.AccessModeRead, "git-upload-pack", "")
|
||||
results, extra = private.ServCommand(ctx, deployKey.KeyID, "user15", "big_test_public_1", perm.AccessModeRead, "git-upload-pack", "")
|
||||
assert.Error(t, extra.Error)
|
||||
assert.Empty(t, results)
|
||||
|
||||
// Add writing deploy key
|
||||
deployKey, err = asymkey_model.AddDeployKey(ctx, 20, "test-deploy", "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGXEEzWmm1dxb+57RoK5KVCL0w2eNv9cqJX2AGGVlkFsVDhOXHzsadS3LTK4VlEbbrDMJdoti9yM8vclA8IeRacAAAAEc3NoOg== nocomment", false)
|
||||
deployKey, err = asymkey_model.AddDeployKey(ctx, 20 /* repo id */, "test-deploy", "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGXEEzWmm1dxb+57RoK5KVCL0w2eNv9cqJX2AGGVlkFsVDhOXHzsadS3LTK4VlEbbrDMJdoti9yM8vclA8IeRacAAAAEc3NoOg== nocomment", false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Cannot push to a private repo with reading key
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAPIUserReposPublicOnly(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly)
|
||||
req := NewRequest(t, "GET", "/api/v1/user/repos").
|
||||
AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var repos []api.Repository
|
||||
DecodeJSON(t, resp, &repos)
|
||||
assert.NotEmpty(t, repos)
|
||||
for _, repo := range repos {
|
||||
assert.False(t, repo.Private)
|
||||
}
|
||||
assert.NotContains(t, repoNames(repos), "user2/repo2")
|
||||
|
||||
req = NewRequest(t, "GET", "/api/v1/users/user2/repos").
|
||||
AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &repos)
|
||||
assert.NotEmpty(t, repos)
|
||||
for _, repo := range repos {
|
||||
assert.False(t, repo.Private)
|
||||
}
|
||||
assert.NotContains(t, repoNames(repos), "user2/repo2")
|
||||
}
|
||||
|
||||
func repoNames(repos []api.Repository) []string {
|
||||
names := make([]string, 0, len(repos))
|
||||
for _, repo := range repos {
|
||||
names = append(names, repo.FullName)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func TestAPIRepoByIDPublicOnly(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly)
|
||||
req := NewRequest(t, "GET", "/api/v1/repositories/1").
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
req = NewRequest(t, "GET", "/api/v1/repositories/2").
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func TestAPIActivityFeedsPublicOnly(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser)
|
||||
req := NewRequest(t, "GET", "/api/v1/users/user2/activities/feeds").
|
||||
AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
var activities []api.Activity
|
||||
DecodeJSON(t, resp, &activities)
|
||||
assert.NotEmpty(t, activities)
|
||||
|
||||
publicToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopePublicOnly)
|
||||
req = NewRequest(t, "GET", "/api/v1/users/user2/activities/feeds").
|
||||
AddTokenAuth(publicToken)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &activities)
|
||||
assertPublicActivitiesOnly(t, activities)
|
||||
|
||||
orgToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadOrganization)
|
||||
req = NewRequest(t, "GET", "/api/v1/orgs/org3/activities/feeds").
|
||||
AddTokenAuth(orgToken)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &activities)
|
||||
assert.NotEmpty(t, activities)
|
||||
|
||||
publicOrgToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopePublicOnly)
|
||||
req = NewRequest(t, "GET", "/api/v1/orgs/org3/activities/feeds").
|
||||
AddTokenAuth(publicOrgToken)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &activities)
|
||||
assertPublicActivitiesOnly(t, activities)
|
||||
}
|
||||
|
||||
func assertPublicActivitiesOnly(t *testing.T, activities []api.Activity) {
|
||||
t.Helper()
|
||||
|
||||
for _, activity := range activities {
|
||||
assert.False(t, activity.IsPrivate)
|
||||
if activity.Repo != nil {
|
||||
assert.False(t, activity.Repo.Private)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,11 @@ import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
pull_service "code.gitea.io/gitea/services/pull"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -362,6 +364,79 @@ func TestAPIPullReviewRequest(t *testing.T) {
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
}
|
||||
|
||||
func TestAPIPullReviewCommentResolveEndpoints(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
ctx := t.Context()
|
||||
pullIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3})
|
||||
require.NoError(t, pullIssue.LoadAttributes(ctx))
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue.RepoID})
|
||||
|
||||
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: pullIssue.PosterID})
|
||||
require.NoError(t, pullIssue.LoadPullRequest(ctx))
|
||||
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
|
||||
require.NoError(t, err)
|
||||
defer gitRepo.Close()
|
||||
|
||||
latestCommitID, err := gitRepo.GetRefCommitID(pullIssue.PullRequest.GetGitHeadRefName())
|
||||
require.NoError(t, err)
|
||||
|
||||
codeComment, err := pull_service.CreateCodeComment(ctx, doer, gitRepo, pullIssue, 1, "resolve comment", "README.md", false, 0, latestCommitID, nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, codeComment)
|
||||
|
||||
session := loginUser(t, doer.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
resolveURL := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/comments/%d/resolve", repo.OwnerName, repo.Name, codeComment.ID)
|
||||
unresolveURL := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/comments/%d/unresolve", repo.OwnerName, repo.Name, codeComment.ID)
|
||||
|
||||
req := NewRequest(t, http.MethodPost, resolveURL).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// Verify comment is resolved
|
||||
updatedComment, err := issues_model.GetCommentByID(ctx, codeComment.ID)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, updatedComment.ResolveDoerID)
|
||||
assert.Equal(t, doer.ID, updatedComment.ResolveDoerID)
|
||||
|
||||
// Resolving again should be idempotent
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
req = NewRequest(t, http.MethodPost, unresolveURL).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// Verify comment is unresolved
|
||||
updatedComment, err = issues_model.GetCommentByID(ctx, codeComment.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Zero(t, updatedComment.ResolveDoerID)
|
||||
|
||||
// Unresolving again should be idempotent
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// Non-existing comment ID
|
||||
req = NewRequest(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/comments/999999/resolve", repo.OwnerName, repo.Name)).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
// Non-code-comment
|
||||
plainComment, err := issue_service.CreateIssueComment(ctx, doer, repo, pullIssue, "not a review comment", nil)
|
||||
require.NoError(t, err)
|
||||
req = NewRequest(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/comments/%d/resolve", repo.OwnerName, repo.Name, plainComment.ID)).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusBadRequest)
|
||||
|
||||
// Test permission check: use a user without write access for target repo to test 403 response
|
||||
unauthorizedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
|
||||
require.NotEqual(t, pullIssue.PosterID, unauthorizedUser.ID)
|
||||
|
||||
unauthorizedSession := loginUser(t, unauthorizedUser.Name)
|
||||
unauthorizedToken := getTokenForLoggedInUser(t, unauthorizedSession, auth_model.AccessTokenScopeWriteIssue, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
req = NewRequest(t, http.MethodGet, fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d", repo.OwnerName, repo.Name, plainComment.ID)).AddTokenAuth(unauthorizedToken)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
req = NewRequest(t, http.MethodPost, resolveURL).AddTokenAuth(unauthorizedToken)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
}
|
||||
|
||||
func TestAPIPullReviewStayDismissed(t *testing.T) {
|
||||
// This test against issue https://github.com/go-gitea/gitea/issues/28542
|
||||
// where old reviews surface after a review request got dismissed.
|
||||
@@ -423,7 +498,7 @@ func TestAPIPullReviewStayDismissed(t *testing.T) {
|
||||
pullIssue.ID, user8.ID, 1, 1, 2, false)
|
||||
|
||||
// user8 dismiss review
|
||||
permUser8, err := access_model.GetUserRepoPermission(t.Context(), pullIssue.Repo, user8)
|
||||
permUser8, err := access_model.GetIndividualUserRepoPermission(t.Context(), pullIssue.Repo, user8)
|
||||
assert.NoError(t, err)
|
||||
_, err = issue_service.ReviewRequest(t.Context(), pullIssue, user8, &permUser8, user8, false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -270,13 +270,20 @@ func TestAPICreatePullSuccess(t *testing.T) {
|
||||
owner11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo11.OwnerID})
|
||||
|
||||
session := loginUser(t, owner11.Name)
|
||||
prTitle := "test pull request title " + time.Now().String()
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &api.CreatePullRequestOption{
|
||||
Head: owner11.Name + ":master",
|
||||
Base: "master",
|
||||
Title: "create a failure pr",
|
||||
Title: prTitle,
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
// Also test that AllowMaintainerEdit is true by default, the "false" case is covered by TestAPICreatePullBasePermission
|
||||
prIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{Title: prTitle})
|
||||
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{IssueID: prIssue.ID})
|
||||
assert.True(t, pr.AllowMaintainerEdit)
|
||||
|
||||
MakeRequest(t, req, http.StatusUnprocessableEntity) // second request should fail
|
||||
}
|
||||
|
||||
@@ -290,11 +297,14 @@ func TestAPICreatePullBasePermission(t *testing.T) {
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
|
||||
session := loginUser(t, user4.Name)
|
||||
prTitle := "test pull request title " + time.Now().String()
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
opts := &api.CreatePullRequestOption{
|
||||
Head: repo11.OwnerName + ":master",
|
||||
Base: "master",
|
||||
Title: "create a failure pr",
|
||||
Title: prTitle,
|
||||
|
||||
AllowMaintainerEdit: new(false),
|
||||
}
|
||||
req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &opts).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
@@ -306,6 +316,11 @@ func TestAPICreatePullBasePermission(t *testing.T) {
|
||||
// create again
|
||||
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &opts).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
// Also test that AllowMaintainerEdit is set to false, the default "true" case is covered by TestAPICreatePullSuccess
|
||||
prIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{Title: prTitle})
|
||||
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{IssueID: prIssue.ID})
|
||||
assert.False(t, pr.AllowMaintainerEdit)
|
||||
}
|
||||
|
||||
func TestAPICreatePullHeadPermission(t *testing.T) {
|
||||
@@ -442,9 +457,8 @@ func TestAPIEditPull(t *testing.T) {
|
||||
Base: "master",
|
||||
Title: title,
|
||||
}).AddTokenAuth(token)
|
||||
apiPull := new(api.PullRequest)
|
||||
resp := MakeRequest(t, req, http.StatusCreated)
|
||||
DecodeJSON(t, resp, apiPull)
|
||||
apiPull := DecodeJSON(t, resp, &api.PullRequest{})
|
||||
assert.Equal(t, "master", apiPull.Base.Name)
|
||||
|
||||
newTitle := "edit a this pr"
|
||||
@@ -455,8 +469,9 @@ func TestAPIEditPull(t *testing.T) {
|
||||
Body: &newBody,
|
||||
}).AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusCreated)
|
||||
DecodeJSON(t, resp, apiPull)
|
||||
apiPull = DecodeJSON(t, resp, &api.PullRequest{})
|
||||
assert.Equal(t, "feature/1", apiPull.Base.Name)
|
||||
|
||||
// check comment history
|
||||
pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID})
|
||||
err := pull.LoadIssue(t.Context())
|
||||
@@ -468,6 +483,62 @@ func TestAPIEditPull(t *testing.T) {
|
||||
Base: "not-exist",
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
t.Run("PullContentVersion", func(t *testing.T) {
|
||||
testAPIPullContentVersion(t, pull.ID)
|
||||
})
|
||||
}
|
||||
|
||||
func testAPIPullContentVersion(t *testing.T, pullID int64) {
|
||||
pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pullID})
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pull.BaseRepoID})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
|
||||
session := loginUser(t, owner.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d", owner.Name, repo.Name, pull.Index)
|
||||
|
||||
t.Run("ResponseIncludesContentVersion", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
req := NewRequest(t, "GET", urlStr).AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
apiPull := DecodeJSON(t, resp, &api.PullRequest{})
|
||||
assert.GreaterOrEqual(t, apiPull.ContentVersion, 0)
|
||||
})
|
||||
|
||||
t.Run("EditWithCorrectVersion", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
req := NewRequest(t, "GET", urlStr).AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
before := DecodeJSON(t, resp, &api.PullRequest{})
|
||||
req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditPullRequestOption{
|
||||
Body: new("updated body with correct version"),
|
||||
ContentVersion: new(before.ContentVersion),
|
||||
}).AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusCreated)
|
||||
after := DecodeJSON(t, resp, &api.PullRequest{})
|
||||
assert.Equal(t, "updated body with correct version", after.Body)
|
||||
assert.Greater(t, after.ContentVersion, before.ContentVersion)
|
||||
})
|
||||
|
||||
t.Run("EditWithWrongVersion", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditPullRequestOption{
|
||||
Body: new("should fail"),
|
||||
ContentVersion: new(99999),
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusConflict)
|
||||
})
|
||||
|
||||
t.Run("EditWithoutVersion", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditPullRequestOption{
|
||||
Body: new("edit without version succeeds"),
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
})
|
||||
}
|
||||
|
||||
func doAPIGetPullFiles(ctx APITestContext, pr *api.PullRequest, callback func(*testing.T, []*api.ChangedFile)) func(*testing.T) {
|
||||
|
||||
@@ -13,15 +13,15 @@ import (
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAPIEditReleaseAttachmentWithUnallowedFile(t *testing.T) {
|
||||
func testAPIEditReleaseAttachmentWithUnallowedFile(t *testing.T) {
|
||||
// Limit the allowed release types (since by default there is no restriction)
|
||||
defer test.MockVariableValue(&setting.Repository.Release.AllowedTypes, ".exe")()
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 9})
|
||||
release := unittest.AssertExistsAndLoadBean(t, &repo_model.Release{ID: attachment.ReleaseID})
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: attachment.RepoID})
|
||||
@@ -38,3 +38,34 @@ func TestAPIEditReleaseAttachmentWithUnallowedFile(t *testing.T) {
|
||||
|
||||
session.MakeRequest(t, req, http.StatusUnprocessableEntity)
|
||||
}
|
||||
|
||||
func testAPIDraftReleaseAttachmentAccess(t *testing.T) {
|
||||
attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 13})
|
||||
release := unittest.AssertExistsAndLoadBean(t, &repo_model.Release{ID: attachment.ReleaseID})
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: attachment.RepoID})
|
||||
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
reader := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
|
||||
listURL := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets", repoOwner.Name, repo.Name, release.ID)
|
||||
getURL := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets/%d", repoOwner.Name, repo.Name, release.ID, attachment.ID)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", listURL), http.StatusNotFound)
|
||||
MakeRequest(t, NewRequest(t, "GET", getURL), http.StatusNotFound)
|
||||
|
||||
readerToken := getUserToken(t, reader.LowerName, auth_model.AccessTokenScopeReadRepository)
|
||||
MakeRequest(t, NewRequest(t, "GET", listURL).AddTokenAuth(readerToken), http.StatusNotFound)
|
||||
MakeRequest(t, NewRequest(t, "GET", getURL).AddTokenAuth(readerToken), http.StatusNotFound)
|
||||
|
||||
ownerReadToken := getUserToken(t, repoOwner.LowerName, auth_model.AccessTokenScopeReadRepository)
|
||||
MakeRequest(t, NewRequest(t, "GET", listURL).AddTokenAuth(ownerReadToken), http.StatusNotFound)
|
||||
MakeRequest(t, NewRequest(t, "GET", getURL).AddTokenAuth(ownerReadToken), http.StatusNotFound)
|
||||
ownerToken := getUserToken(t, repoOwner.LowerName, auth_model.AccessTokenScopeWriteRepository)
|
||||
resp := MakeRequest(t, NewRequest(t, "GET", listURL).AddTokenAuth(ownerToken), http.StatusOK)
|
||||
var attachments []*api.Attachment
|
||||
DecodeJSON(t, resp, &attachments)
|
||||
if assert.Len(t, attachments, 1) {
|
||||
assert.Equal(t, attachment.ID, attachments[0].ID)
|
||||
}
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", getURL).AddTokenAuth(ownerToken), http.StatusOK)
|
||||
}
|
||||
|
||||
@@ -29,12 +29,22 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAPIListReleases(t *testing.T) {
|
||||
func TestAPIReleaseRead(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
t.Run("DraftReleaseAttachmentAccess", testAPIDraftReleaseAttachmentAccess)
|
||||
t.Run("ListReleasesWithWriteToken", testAPIListReleasesWithWriteToken)
|
||||
t.Run("ListReleasesWithReadToken", testAPIListReleasesWithReadToken)
|
||||
t.Run("GetDraftRelease", testAPIGetDraftRelease)
|
||||
t.Run("GetLatestRelease", testAPIGetLatestRelease)
|
||||
t.Run("GetReleaseByTag", testAPIGetReleaseByTag)
|
||||
t.Run("GetDraftReleaseByTag", testAPIGetDraftReleaseByTag)
|
||||
t.Run("EditReleaseAttachmentWithUnallowedFile", testAPIEditReleaseAttachmentWithUnallowedFile) // failed attempt, so it is also a read test
|
||||
}
|
||||
|
||||
func testAPIListReleasesWithWriteToken(t *testing.T) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
token := getUserToken(t, user2.LowerName, auth_model.AccessTokenScopeReadRepository)
|
||||
token := getUserToken(t, user2.LowerName, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/releases", user2.Name, repo.Name))
|
||||
resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK)
|
||||
@@ -81,6 +91,72 @@ func TestAPIListReleases(t *testing.T) {
|
||||
testFilterByLen(true, url.Values{"draft": {"true"}, "pre-release": {"true"}}, 0, "there is no pre-release draft")
|
||||
}
|
||||
|
||||
func testAPIListReleasesWithReadToken(t *testing.T) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
token := getUserToken(t, user2.LowerName, auth_model.AccessTokenScopeReadRepository)
|
||||
|
||||
link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/releases", user2.Name, repo.Name))
|
||||
resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK)
|
||||
var apiReleases []*api.Release
|
||||
DecodeJSON(t, resp, &apiReleases)
|
||||
if assert.Len(t, apiReleases, 2) {
|
||||
for _, release := range apiReleases {
|
||||
switch release.ID {
|
||||
case 1:
|
||||
assert.False(t, release.IsDraft)
|
||||
assert.False(t, release.IsPrerelease)
|
||||
assert.True(t, strings.HasSuffix(release.UploadURL, "/api/v1/repos/user2/repo1/releases/1/assets"), release.UploadURL)
|
||||
case 5:
|
||||
assert.False(t, release.IsDraft)
|
||||
assert.True(t, release.IsPrerelease)
|
||||
assert.True(t, strings.HasSuffix(release.UploadURL, "/api/v1/repos/user2/repo1/releases/5/assets"), release.UploadURL)
|
||||
default:
|
||||
assert.NoError(t, fmt.Errorf("unexpected release: %v", release))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// test filter
|
||||
testFilterByLen := func(auth bool, query url.Values, expectedLength int, msgAndArgs ...string) {
|
||||
link.RawQuery = query.Encode()
|
||||
req := NewRequest(t, "GET", link.String())
|
||||
if auth {
|
||||
req.AddTokenAuth(token)
|
||||
}
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &apiReleases)
|
||||
assert.Len(t, apiReleases, expectedLength, msgAndArgs)
|
||||
}
|
||||
|
||||
testFilterByLen(false, url.Values{"draft": {"true"}}, 0, "anon should not see drafts")
|
||||
testFilterByLen(true, url.Values{"draft": {"true"}}, 0, "repo owner with read token should not see drafts")
|
||||
testFilterByLen(true, url.Values{"draft": {"false"}}, 2, "exclude drafts")
|
||||
testFilterByLen(true, url.Values{"draft": {"false"}, "pre-release": {"false"}}, 1, "exclude drafts and pre-releases")
|
||||
testFilterByLen(true, url.Values{"pre-release": {"true"}}, 1, "only get pre-release")
|
||||
testFilterByLen(true, url.Values{"draft": {"true"}, "pre-release": {"true"}}, 0, "there is no pre-release draft")
|
||||
}
|
||||
|
||||
func testAPIGetDraftRelease(t *testing.T) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
release := unittest.AssertExistsAndLoadBean(t, &repo_model.Release{ID: 4})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
reader := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
|
||||
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d", owner.Name, repo.Name, release.ID)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", urlStr), http.StatusNotFound)
|
||||
|
||||
readerToken := getUserToken(t, reader.LowerName, auth_model.AccessTokenScopeReadRepository)
|
||||
MakeRequest(t, NewRequest(t, "GET", urlStr).AddTokenAuth(readerToken), http.StatusNotFound)
|
||||
|
||||
ownerToken := getUserToken(t, owner.LowerName, auth_model.AccessTokenScopeWriteRepository)
|
||||
resp := MakeRequest(t, NewRequest(t, "GET", urlStr).AddTokenAuth(ownerToken), http.StatusOK)
|
||||
var apiRelease api.Release
|
||||
DecodeJSON(t, resp, &apiRelease)
|
||||
assert.Equal(t, release.Title, apiRelease.Title)
|
||||
}
|
||||
|
||||
func createNewReleaseUsingAPI(t *testing.T, token string, owner *user_model.User, repo *repo_model.Repository, name, target, title, desc string) *api.Release {
|
||||
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases", owner.Name, repo.Name)
|
||||
req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateReleaseOption{
|
||||
@@ -230,9 +306,7 @@ func TestAPICreateReleaseGivenInvalidTarget(t *testing.T) {
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func TestAPIGetLatestRelease(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
func testAPIGetLatestRelease(t *testing.T) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
|
||||
@@ -245,9 +319,7 @@ func TestAPIGetLatestRelease(t *testing.T) {
|
||||
assert.Equal(t, "testing-release", release.Title)
|
||||
}
|
||||
|
||||
func TestAPIGetReleaseByTag(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
func testAPIGetReleaseByTag(t *testing.T) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
|
||||
@@ -271,9 +343,7 @@ func TestAPIGetReleaseByTag(t *testing.T) {
|
||||
assert.NotEmpty(t, err.Message)
|
||||
}
|
||||
|
||||
func TestAPIGetDraftReleaseByTag(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
func testAPIGetDraftReleaseByTag(t *testing.T) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
|
||||
@@ -335,7 +405,7 @@ func TestAPIDeleteReleaseByTagName(t *testing.T) {
|
||||
|
||||
func TestAPIUploadAssetRelease(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
defer test.MockVariableValue(&setting.Attachment.MaxSize, 1)()
|
||||
defer test.MockVariableValue(&setting.Repository.Release.FileMaxSize, 1)()
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
@@ -352,7 +422,7 @@ func TestAPIUploadAssetRelease(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
const filename = "image.png"
|
||||
|
||||
performUpload := func(t *testing.T, uploadURL string, buf []byte, expectedStatus int) *httptest.ResponseRecorder {
|
||||
performUpload := func(t *testing.T, uploadURL string, _ []byte, _ int) *httptest.ResponseRecorder {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
part, err := writer.CreateFormFile("attachment", filename)
|
||||
|
||||
@@ -29,10 +29,10 @@ func TestAPIRepoBranchesPlain(t *testing.T) {
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
session := loginUser(t, user1.LowerName)
|
||||
|
||||
// public only token should be forbidden
|
||||
// public-only token cannot see a private repo
|
||||
publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopePublicOnly, auth_model.AccessTokenScopeWriteRepository)
|
||||
link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches", repo3.Name)) // a plain repo
|
||||
MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound)
|
||||
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK)
|
||||
@@ -46,7 +46,7 @@ func TestAPIRepoBranchesPlain(t *testing.T) {
|
||||
assert.Equal(t, "master", branches[1].Name)
|
||||
|
||||
link2, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch", repo3.Name))
|
||||
MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound)
|
||||
|
||||
resp = MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(token), http.StatusOK)
|
||||
bs, err = io.ReadAll(resp.Body)
|
||||
@@ -55,7 +55,7 @@ func TestAPIRepoBranchesPlain(t *testing.T) {
|
||||
assert.NoError(t, json.Unmarshal(bs, &branch))
|
||||
assert.Equal(t, "test_branch", branch.Name)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "POST", link.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "POST", link.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound)
|
||||
|
||||
req := NewRequest(t, "POST", link.String()).AddTokenAuth(token)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
@@ -81,7 +81,7 @@ func TestAPIRepoBranchesPlain(t *testing.T) {
|
||||
|
||||
link3, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch2", repo3.Name))
|
||||
MakeRequest(t, NewRequest(t, "DELETE", link3.String()), http.StatusNotFound)
|
||||
MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(token), http.StatusNoContent)
|
||||
assert.NoError(t, err)
|
||||
@@ -121,10 +121,10 @@ func TestAPIRepoBranchesMirror(t *testing.T) {
|
||||
resp = MakeRequest(t, req, http.StatusForbidden)
|
||||
bs, err = io.ReadAll(resp.Body)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}\n", string(bs))
|
||||
assert.JSONEq(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}", string(bs))
|
||||
|
||||
resp = MakeRequest(t, NewRequest(t, "DELETE", link2.String()).AddTokenAuth(token), http.StatusForbidden)
|
||||
bs, err = io.ReadAll(resp.Body)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}\n", string(bs))
|
||||
assert.JSONEq(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}", string(bs))
|
||||
}
|
||||
|
||||
@@ -5,46 +5,60 @@ package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAPICompareBranches(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
onGiteaRun(t, func(t *testing.T, _ *url.URL) {
|
||||
session2 := loginUser(t, "user2")
|
||||
token2 := getTokenForLoggedInUser(t, session2, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
// Login as User2.
|
||||
session := loginUser(t, user.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
t.Run("CompareBranches", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
t.Run("CompareBranches", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo20/compare/add-csv...remove-files-b").AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo20/compare/add-csv...remove-files-b").AddTokenAuth(token2)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
apiResp := DecodeJSON(t, resp, &api.Compare{})
|
||||
assert.Equal(t, 2, apiResp.TotalCommits)
|
||||
assert.Len(t, apiResp.Commits, 2)
|
||||
})
|
||||
|
||||
var apiResp *api.Compare
|
||||
DecodeJSON(t, resp, &apiResp)
|
||||
t.Run("CompareCommits", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
assert.Equal(t, 2, apiResp.TotalCommits)
|
||||
assert.Len(t, apiResp.Commits, 2)
|
||||
})
|
||||
req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo20/compare/808038d2f71b0ab02099...c8e31bc7688741a5287f").AddTokenAuth(token2)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
apiResp := DecodeJSON(t, resp, &api.Compare{})
|
||||
assert.Equal(t, 1, apiResp.TotalCommits)
|
||||
assert.Len(t, apiResp.Commits, 1)
|
||||
})
|
||||
|
||||
t.Run("CompareCommits", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo20/compare/808038d2f71b0ab02099...c8e31bc7688741a5287f").AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
t.Run("CompareForkOnlyCommit", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
var apiResp *api.Compare
|
||||
DecodeJSON(t, resp, &apiResp)
|
||||
user13 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 13})
|
||||
repo11 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 11})
|
||||
user13Sess := loginUser(t, "user13")
|
||||
user13Token := getTokenForLoggedInUser(t, user13Sess, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
assert.Equal(t, 1, apiResp.TotalCommits)
|
||||
assert.Len(t, apiResp.Commits, 1)
|
||||
_, err := createFileInBranch(user13, repo11, createFileInBranchOptions{OldBranch: "master", NewBranch: "new-branch"}, map[string]string{"file.txt": "content"})
|
||||
require.NoError(t, err)
|
||||
req := NewRequestf(t, "GET", "/api/v1/repos/user12/repo10/compare/master...user13:new-branch").AddTokenAuth(user13Token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
apiResp := DecodeJSON(t, resp, &api.Compare{})
|
||||
assert.Equal(t, 1, apiResp.TotalCommits)
|
||||
assert.Len(t, apiResp.Commits, 1)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -418,5 +418,20 @@ func TestAPIRepoEdit(t *testing.T) {
|
||||
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo1.Name), &repoEditOption).
|
||||
AddTokenAuth(token4)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
|
||||
// Test updating pull request settings without setting has_pull_requests
|
||||
repo1 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
url = fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo1.Name)
|
||||
req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{
|
||||
DefaultDeleteBranchAfterMerge: &bTrue,
|
||||
}).AddTokenAuth(token2)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &repo)
|
||||
assert.True(t, repo.DefaultDeleteBranchAfterMerge)
|
||||
// reset
|
||||
req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{
|
||||
DefaultDeleteBranchAfterMerge: &bFalse,
|
||||
}).AddTokenAuth(token2)
|
||||
_ = MakeRequest(t, req, http.StatusOK)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -53,8 +52,8 @@ func getCreateFileOptions() api.CreateFileOptions {
|
||||
func normalizeFileContentResponseCommitTime(c *api.ContentsResponse) {
|
||||
// decoded JSON response may contain different timezone from the one parsed by git commit
|
||||
// so we need to normalize the time to UTC to make "assert.Equal" pass
|
||||
c.LastCommitterDate = util.ToPointer(c.LastCommitterDate.UTC())
|
||||
c.LastAuthorDate = util.ToPointer(c.LastAuthorDate.UTC())
|
||||
c.LastCommitterDate = new(c.LastCommitterDate.UTC())
|
||||
c.LastAuthorDate = new(c.LastAuthorDate.UTC())
|
||||
}
|
||||
|
||||
type apiFileResponseInfo struct {
|
||||
@@ -75,9 +74,9 @@ func getExpectedFileResponseForCreate(info apiFileResponseInfo) *api.FileRespons
|
||||
Name: path.Base(info.treePath),
|
||||
Path: info.treePath,
|
||||
SHA: sha,
|
||||
LastCommitSHA: util.ToPointer(info.lastCommitSHA),
|
||||
LastCommitterDate: util.ToPointer(info.lastCommitterWhen),
|
||||
LastAuthorDate: util.ToPointer(info.lastAuthorWhen),
|
||||
LastCommitSHA: new(info.lastCommitSHA),
|
||||
LastCommitterDate: new(info.lastCommitterWhen),
|
||||
LastAuthorDate: new(info.lastAuthorWhen),
|
||||
Size: 16,
|
||||
Type: "file",
|
||||
Encoding: &encoding,
|
||||
|
||||
@@ -20,21 +20,19 @@ import (
|
||||
|
||||
func getDeleteFileOptions() *api.DeleteFileOptions {
|
||||
return &api.DeleteFileOptions{
|
||||
FileOptionsWithSHA: api.FileOptionsWithSHA{
|
||||
FileOptions: api.FileOptions{
|
||||
BranchName: "master",
|
||||
NewBranchName: "master",
|
||||
Message: "Removing the file new/file.txt",
|
||||
Author: api.Identity{
|
||||
Name: "John Doe",
|
||||
Email: "johndoe@example.com",
|
||||
},
|
||||
Committer: api.Identity{
|
||||
Name: "Jane Doe",
|
||||
Email: "janedoe@example.com",
|
||||
},
|
||||
SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885",
|
||||
FileOptions: api.FileOptions{
|
||||
BranchName: "master",
|
||||
NewBranchName: "master",
|
||||
Message: "Removing the file new/file.txt",
|
||||
Author: api.Identity{
|
||||
Name: "John Doe",
|
||||
Email: "johndoe@example.com",
|
||||
},
|
||||
Committer: api.Identity{
|
||||
Name: "Jane Doe",
|
||||
Email: "janedoe@example.com",
|
||||
},
|
||||
SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"testing"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -25,8 +24,9 @@ func TestAPIGetRawFileOrLFS(t *testing.T) {
|
||||
|
||||
// Test with LFS
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
createLFSTestRepository(t, "repo-lfs-test")
|
||||
httpContext := NewAPITestContext(t, "user2", "repo-lfs-test", auth_model.AccessTokenScopeWriteRepository)
|
||||
doAPICreateRepository(httpContext, false, func(t *testing.T, repository api.Repository) {
|
||||
t.Run("repo-lfs-test", func(t *testing.T) {
|
||||
u.Path = httpContext.GitPath()
|
||||
dstPath := t.TempDir()
|
||||
|
||||
@@ -41,7 +41,7 @@ func TestAPIGetRawFileOrLFS(t *testing.T) {
|
||||
|
||||
lfs := lfsCommitAndPushTest(t, dstPath, testFileSizeSmall)[0]
|
||||
|
||||
reqLFS := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/media/"+lfs)
|
||||
reqLFS := NewRequest(t, "GET", "/api/v1/repos/user2/repo-lfs-test/media/"+lfs).AddTokenAuth(httpContext.Token)
|
||||
respLFS := MakeRequestNilResponseRecorder(t, reqLFS, http.StatusOK)
|
||||
assert.Equal(t, testFileSizeSmall, respLFS.Length)
|
||||
})
|
||||
|
||||
@@ -5,6 +5,7 @@ package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -19,6 +20,9 @@ import (
|
||||
|
||||
type createFileInBranchOptions struct {
|
||||
OldBranch, NewBranch string
|
||||
CommitMessage string
|
||||
CommitterName string
|
||||
CommitterEmail string
|
||||
}
|
||||
|
||||
func testCreateFileInBranch(t *testing.T, user *user_model.User, repo *repo_model.Repository, createOpts createFileInBranchOptions, files map[string]string) *api.FilesResponse {
|
||||
@@ -29,7 +33,17 @@ func testCreateFileInBranch(t *testing.T, user *user_model.User, repo *repo_mode
|
||||
|
||||
func createFileInBranch(user *user_model.User, repo *repo_model.Repository, createOpts createFileInBranchOptions, files map[string]string) (*api.FilesResponse, error) {
|
||||
ctx := context.TODO()
|
||||
opts := &files_service.ChangeRepoFilesOptions{OldBranch: createOpts.OldBranch, NewBranch: createOpts.NewBranch}
|
||||
opts := &files_service.ChangeRepoFilesOptions{
|
||||
OldBranch: createOpts.OldBranch,
|
||||
NewBranch: createOpts.NewBranch,
|
||||
Message: createOpts.CommitMessage,
|
||||
}
|
||||
if createOpts.CommitterName != "" || createOpts.CommitterEmail != "" {
|
||||
opts.Committer = &files_service.IdentityOptions{
|
||||
GitUserName: createOpts.CommitterName,
|
||||
GitUserEmail: createOpts.CommitterEmail,
|
||||
}
|
||||
}
|
||||
for path, content := range files {
|
||||
opts.Files = append(opts.Files, &files_service.ChangeRepoFile{
|
||||
Operation: "create",
|
||||
@@ -59,7 +73,7 @@ func deleteFileInBranch(user *user_model.User, repo *repo_model.Repository, tree
|
||||
func createOrReplaceFileInBranch(user *user_model.User, repo *repo_model.Repository, treePath, branchName, content string) error {
|
||||
_, err := deleteFileInBranch(user, repo, treePath, branchName)
|
||||
|
||||
if err != nil && !files_service.IsErrRepoFileDoesNotExist(err) {
|
||||
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -28,21 +27,19 @@ func getUpdateFileOptions() *api.UpdateFileOptions {
|
||||
content := "This is updated text"
|
||||
contentEncoded := base64.StdEncoding.EncodeToString([]byte(content))
|
||||
return &api.UpdateFileOptions{
|
||||
FileOptionsWithSHA: api.FileOptionsWithSHA{
|
||||
FileOptions: api.FileOptions{
|
||||
BranchName: "master",
|
||||
NewBranchName: "master",
|
||||
Message: "My update of new/file.txt",
|
||||
Author: api.Identity{
|
||||
Name: "John Doe",
|
||||
Email: "johndoe@example.com",
|
||||
},
|
||||
Committer: api.Identity{
|
||||
Name: "Anne Doe",
|
||||
Email: "annedoe@example.com",
|
||||
},
|
||||
SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885",
|
||||
FileOptions: api.FileOptions{
|
||||
BranchName: "master",
|
||||
NewBranchName: "master",
|
||||
Message: "My update of new/file.txt",
|
||||
Author: api.Identity{
|
||||
Name: "John Doe",
|
||||
Email: "johndoe@example.com",
|
||||
},
|
||||
Committer: api.Identity{
|
||||
Name: "Anne Doe",
|
||||
Email: "annedoe@example.com",
|
||||
},
|
||||
SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885",
|
||||
},
|
||||
ContentBase64: contentEncoded,
|
||||
}
|
||||
@@ -61,9 +58,9 @@ func getExpectedFileResponseForUpdate(info apiFileResponseInfo) *api.FileRespons
|
||||
Name: path.Base(info.treePath),
|
||||
Path: info.treePath,
|
||||
SHA: sha,
|
||||
LastCommitSHA: util.ToPointer(info.lastCommitSHA),
|
||||
LastCommitterDate: util.ToPointer(info.lastCommitterWhen),
|
||||
LastAuthorDate: util.ToPointer(info.lastAuthorWhen),
|
||||
LastCommitSHA: new(info.lastCommitSHA),
|
||||
LastCommitterDate: new(info.lastCommitterWhen),
|
||||
LastAuthorDate: new(info.lastAuthorWhen),
|
||||
Type: "file",
|
||||
Size: 20,
|
||||
Encoding: &encoding,
|
||||
@@ -180,6 +177,15 @@ func TestAPIUpdateFile(t *testing.T) {
|
||||
assert.Equal(t, expectedDownloadURL, *fileResponse.Content.DownloadURL)
|
||||
assert.Equal(t, updateFileOptions.Message+"\n", fileResponse.Commit.Message)
|
||||
|
||||
// Test updating a file without SHA (should create the file)
|
||||
updateFileOptions = getUpdateFileOptions()
|
||||
updateFileOptions.SHA = ""
|
||||
req = NewRequestWithJSON(t, "PUT", "/api/v1/repos/user2/repo1/contents/update-create.txt", &updateFileOptions).AddTokenAuth(token2)
|
||||
resp = MakeRequest(t, req, http.StatusCreated)
|
||||
DecodeJSON(t, resp, &fileResponse)
|
||||
assert.Equal(t, "08bd14b2e2852529157324de9c226b3364e76136", fileResponse.Content.SHA)
|
||||
assert.Equal(t, setting.AppURL+"user2/repo1/raw/branch/master/update-create.txt", *fileResponse.Content.DownloadURL)
|
||||
|
||||
// Test updating a file and renaming it
|
||||
updateFileOptions = getUpdateFileOptions()
|
||||
updateFileOptions.BranchName = repo1.DefaultBranch
|
||||
|
||||
@@ -161,9 +161,88 @@ func TestAPIChangeFiles(t *testing.T) {
|
||||
assert.Equal(t, expectedUpdateHTMLURL, *filesResponse.Files[1].HTMLURL)
|
||||
assert.Equal(t, expectedUpdateDownloadURL, *filesResponse.Files[1].DownloadURL)
|
||||
assert.Nil(t, filesResponse.Files[2])
|
||||
|
||||
assert.Equal(t, changeFilesOptions.Message+"\n", filesResponse.Commit.Message)
|
||||
|
||||
// Test fails creating a file in a branch that already exists without force
|
||||
changeFilesOptions = getChangeFilesOptions()
|
||||
changeFilesOptions.BranchName = repo1.DefaultBranch
|
||||
changeFilesOptions.NewBranchName = "develop"
|
||||
changeFilesOptions.ForcePush = false
|
||||
fileID++
|
||||
createTreePath = fmt.Sprintf("new/file%d.txt", fileID)
|
||||
updateTreePath = fmt.Sprintf("update/file%d.txt", fileID)
|
||||
deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID)
|
||||
changeFilesOptions.Files[0].Path = createTreePath
|
||||
changeFilesOptions.Files[1].Path = updateTreePath
|
||||
changeFilesOptions.Files[2].Path = deleteTreePath
|
||||
createFile(user2, repo1, updateTreePath)
|
||||
createFile(user2, repo1, deleteTreePath)
|
||||
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name)
|
||||
req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions).
|
||||
AddTokenAuth(token2)
|
||||
resp = MakeRequest(t, req, http.StatusUnprocessableEntity)
|
||||
assert.Contains(t, resp.Body.String(), `"message":"branch already exists [name: develop]"`)
|
||||
|
||||
// Test succeeds creating a file in a branch that already exists with force
|
||||
changeFilesOptions = getChangeFilesOptions()
|
||||
changeFilesOptions.BranchName = repo1.DefaultBranch
|
||||
changeFilesOptions.NewBranchName = "develop"
|
||||
changeFilesOptions.ForcePush = true
|
||||
fileID++
|
||||
createTreePath = fmt.Sprintf("new/file%d.txt", fileID)
|
||||
updateTreePath = fmt.Sprintf("update/file%d.txt", fileID)
|
||||
deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID)
|
||||
changeFilesOptions.Files[0].Path = createTreePath
|
||||
changeFilesOptions.Files[1].Path = updateTreePath
|
||||
changeFilesOptions.Files[2].Path = deleteTreePath
|
||||
createFile(user2, repo1, updateTreePath)
|
||||
createFile(user2, repo1, deleteTreePath)
|
||||
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name)
|
||||
req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions).
|
||||
AddTokenAuth(token2)
|
||||
resp = MakeRequest(t, req, http.StatusCreated)
|
||||
DecodeJSON(t, resp, &filesResponse)
|
||||
expectedCreateHTMLURL = fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/develop/new/file%d.txt", fileID)
|
||||
expectedCreateDownloadURL = fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/develop/new/file%d.txt", fileID)
|
||||
expectedUpdateHTMLURL = fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/develop/update/file%d.txt", fileID)
|
||||
expectedUpdateDownloadURL = fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/develop/update/file%d.txt", fileID)
|
||||
assert.Equal(t, expectedCreateSHA, filesResponse.Files[0].SHA)
|
||||
assert.Equal(t, expectedCreateHTMLURL, *filesResponse.Files[0].HTMLURL)
|
||||
assert.Equal(t, expectedCreateDownloadURL, *filesResponse.Files[0].DownloadURL)
|
||||
assert.Equal(t, expectedUpdateSHA, filesResponse.Files[1].SHA)
|
||||
assert.Equal(t, expectedUpdateHTMLURL, *filesResponse.Files[1].HTMLURL)
|
||||
assert.Equal(t, expectedUpdateDownloadURL, *filesResponse.Files[1].DownloadURL)
|
||||
assert.Nil(t, filesResponse.Files[2])
|
||||
assert.Equal(t, changeFilesOptions.Message+"\n", filesResponse.Commit.Message)
|
||||
|
||||
// Test fails creating a file in a branch that already exists with force and branch protection enabled
|
||||
protectionReq := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/branch_protections", &api.BranchProtection{
|
||||
RuleName: "develop",
|
||||
BranchName: "develop",
|
||||
Priority: 1,
|
||||
EnablePush: true,
|
||||
EnableForcePush: false,
|
||||
}).AddTokenAuth(token2)
|
||||
MakeRequest(t, protectionReq, http.StatusCreated)
|
||||
changeFilesOptions = getChangeFilesOptions()
|
||||
changeFilesOptions.BranchName = repo1.DefaultBranch
|
||||
changeFilesOptions.NewBranchName = "develop"
|
||||
changeFilesOptions.ForcePush = true
|
||||
fileID++
|
||||
createTreePath = fmt.Sprintf("new/file%d.txt", fileID)
|
||||
updateTreePath = fmt.Sprintf("update/file%d.txt", fileID)
|
||||
deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID)
|
||||
changeFilesOptions.Files[0].Path = createTreePath
|
||||
changeFilesOptions.Files[1].Path = updateTreePath
|
||||
changeFilesOptions.Files[2].Path = deleteTreePath
|
||||
createFile(user2, repo1, updateTreePath)
|
||||
createFile(user2, repo1, deleteTreePath)
|
||||
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name)
|
||||
req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions).
|
||||
AddTokenAuth(token2)
|
||||
resp = MakeRequest(t, req, http.StatusForbidden)
|
||||
assert.Contains(t, resp.Body.String(), `"message":"branch develop is protected from force push"`)
|
||||
|
||||
// Test updating a file and renaming it
|
||||
changeFilesOptions = getChangeFilesOptions()
|
||||
changeFilesOptions.BranchName = repo1.DefaultBranch
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -35,9 +34,9 @@ func getExpectedContentsListResponseForContents(ref, refType, lastCommitSHA stri
|
||||
Name: path.Base(treePath),
|
||||
Path: treePath,
|
||||
SHA: sha,
|
||||
LastCommitSHA: util.ToPointer(lastCommitSHA),
|
||||
LastCommitterDate: util.ToPointer(time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400))),
|
||||
LastAuthorDate: util.ToPointer(time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400))),
|
||||
LastCommitSHA: new(lastCommitSHA),
|
||||
LastCommitterDate: new(time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400))),
|
||||
LastAuthorDate: new(time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400))),
|
||||
Type: "file",
|
||||
Size: 30,
|
||||
URL: &selfURL,
|
||||
@@ -80,7 +79,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) {
|
||||
|
||||
// Make a new branch in repo1
|
||||
newBranch := "test_branch"
|
||||
err = repo_service.CreateNewBranch(t.Context(), user2, repo1, gitRepo, repo1.DefaultBranch, newBranch)
|
||||
err = repo_service.CreateNewBranch(t.Context(), user2, repo1, repo1.DefaultBranch, newBranch)
|
||||
assert.NoError(t, err)
|
||||
|
||||
commitID, _ := gitRepo.GetBranchCommitID(repo1.DefaultBranch)
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -34,17 +33,17 @@ func getExpectedContentsResponseForContents(ref, refType, lastCommitSHA string)
|
||||
Name: treePath,
|
||||
Path: treePath,
|
||||
SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f",
|
||||
LastCommitSHA: util.ToPointer(lastCommitSHA),
|
||||
LastCommitterDate: util.ToPointer(time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400))),
|
||||
LastAuthorDate: util.ToPointer(time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400))),
|
||||
LastCommitSHA: new(lastCommitSHA),
|
||||
LastCommitterDate: new(time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400))),
|
||||
LastAuthorDate: new(time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400))),
|
||||
Type: "file",
|
||||
Size: 30,
|
||||
Encoding: util.ToPointer("base64"),
|
||||
Content: util.ToPointer("IyByZXBvMQoKRGVzY3JpcHRpb24gZm9yIHJlcG8x"),
|
||||
Encoding: new("base64"),
|
||||
Content: new("IyByZXBvMQoKRGVzY3JpcHRpb24gZm9yIHJlcG8x"),
|
||||
URL: &selfURL,
|
||||
HTMLURL: &htmlURL,
|
||||
GitURL: &gitURL,
|
||||
DownloadURL: util.ToPointer(setting.AppURL + "user2/repo1/raw/" + refType + "/" + ref + "/" + treePath),
|
||||
DownloadURL: new(setting.AppURL + "user2/repo1/raw/" + refType + "/" + ref + "/" + treePath),
|
||||
Links: &api.FileLinksResponse{
|
||||
Self: &selfURL,
|
||||
GitURL: &gitURL,
|
||||
@@ -61,7 +60,7 @@ func TestAPIGetContents(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func testAPIGetContents(t *testing.T, u *url.URL) {
|
||||
func testAPIGetContents(t *testing.T, _ *url.URL) {
|
||||
/*** SETUP ***/
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16
|
||||
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3, is an org
|
||||
@@ -85,7 +84,7 @@ func testAPIGetContents(t *testing.T, u *url.URL) {
|
||||
|
||||
// Make a new branch in repo1
|
||||
newBranch := "test_branch"
|
||||
err = repo_service.CreateNewBranch(t.Context(), user2, repo1, gitRepo, repo1.DefaultBranch, newBranch)
|
||||
err = repo_service.CreateNewBranch(t.Context(), user2, repo1, repo1.DefaultBranch, newBranch)
|
||||
require.NoError(t, err)
|
||||
|
||||
commitID, err := gitRepo.GetBranchCommitID(repo1.DefaultBranch)
|
||||
@@ -256,8 +255,8 @@ func testAPIGetContentsExt(t *testing.T) {
|
||||
assert.Equal(t, "jpeg.jpg", respFile.Name)
|
||||
assert.Nil(t, respFile.Encoding)
|
||||
assert.Nil(t, respFile.Content)
|
||||
assert.Equal(t, util.ToPointer(int64(107)), respFile.LfsSize)
|
||||
assert.Equal(t, util.ToPointer("0b8d8b5f15046343fd32f451df93acc2bdd9e6373be478b968e4cad6b6647351"), respFile.LfsOid)
|
||||
assert.Equal(t, new(int64(107)), respFile.LfsSize)
|
||||
assert.Equal(t, new("0b8d8b5f15046343fd32f451df93acc2bdd9e6373be478b968e4cad6b6647351"), respFile.LfsOid)
|
||||
})
|
||||
t.Run("FileContents", func(t *testing.T) {
|
||||
// by default, no file content or commit info is returned
|
||||
@@ -296,7 +295,7 @@ func testAPIGetContentsExt(t *testing.T) {
|
||||
assert.NotNil(t, respFile.Content)
|
||||
assert.Nil(t, contentsResponse.FileContents.LastCommitSHA)
|
||||
assert.Nil(t, contentsResponse.FileContents.LastCommitMessage)
|
||||
assert.Equal(t, util.ToPointer(int64(107)), respFile.LfsSize)
|
||||
assert.Equal(t, util.ToPointer("0b8d8b5f15046343fd32f451df93acc2bdd9e6373be478b968e4cad6b6647351"), respFile.LfsOid)
|
||||
assert.Equal(t, new(int64(107)), respFile.LfsSize)
|
||||
assert.Equal(t, new("0b8d8b5f15046343fd32f451df93acc2bdd9e6373be478b968e4cad6b6647351"), respFile.LfsOid)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -12,10 +12,8 @@ import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -30,8 +28,8 @@ func TestAPIGitTags(t *testing.T) {
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
|
||||
|
||||
// Set up git config for the tagger
|
||||
_ = gitcmd.NewCommand("config", "user.name").AddDynamicArguments(user.Name).Run(t.Context(), &gitcmd.RunOpts{Dir: repo.RepoPath()})
|
||||
_ = gitcmd.NewCommand("config", "user.email").AddDynamicArguments(user.Email).Run(t.Context(), &gitcmd.RunOpts{Dir: repo.RepoPath()})
|
||||
_ = gitrepo.GitConfigSet(t.Context(), repo, "user.name", user.Name)
|
||||
_ = gitrepo.GitConfigSet(t.Context(), repo, "user.email", user.Email)
|
||||
|
||||
gitRepo, _ := gitrepo.OpenRepository(t.Context(), repo)
|
||||
defer gitRepo.Close()
|
||||
@@ -59,7 +57,7 @@ func TestAPIGitTags(t *testing.T) {
|
||||
assert.Equal(t, aTagMessage+"\n", tag.Message)
|
||||
assert.Equal(t, user.Name, tag.Tagger.Name)
|
||||
assert.Equal(t, user.Email, tag.Tagger.Email)
|
||||
assert.Equal(t, util.URLJoin(repo.APIURL(), "git/tags", aTag.ID.String()), tag.URL)
|
||||
assert.Equal(t, repo.APIURL()+"/git/tags/"+aTag.ID.String(), tag.URL)
|
||||
|
||||
// Should NOT work for lightweight tags
|
||||
badReq := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/tags/%s", user.Name, repo.Name, commit.ID.String()).
|
||||
|
||||
@@ -34,6 +34,7 @@ func TestAPICreateHook(t *testing.T) {
|
||||
"url": "http://example.com/",
|
||||
},
|
||||
AuthorizationHeader: "Bearer s3cr3t",
|
||||
Name: " CI notifications ",
|
||||
}).AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
@@ -41,4 +42,54 @@ func TestAPICreateHook(t *testing.T) {
|
||||
DecodeJSON(t, resp, &apiHook)
|
||||
assert.Equal(t, "http://example.com/", apiHook.Config["url"])
|
||||
assert.Equal(t, "Bearer s3cr3t", apiHook.AuthorizationHeader)
|
||||
assert.Equal(t, "CI notifications", apiHook.Name)
|
||||
|
||||
newName := "Deploy hook"
|
||||
patchReq := NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s/hooks/%d", owner.Name, repo.Name, apiHook.ID), api.EditHookOption{
|
||||
Name: &newName,
|
||||
}).AddTokenAuth(token)
|
||||
patchResp := MakeRequest(t, patchReq, http.StatusOK)
|
||||
var patched *api.Hook
|
||||
DecodeJSON(t, patchResp, &patched)
|
||||
assert.Equal(t, newName, patched.Name)
|
||||
|
||||
hooksURL := fmt.Sprintf("/api/v1/repos/%s/%s/hooks", owner.Name, repo.Name)
|
||||
|
||||
// Create with Name field omitted: Name should be ""
|
||||
req2 := NewRequestWithJSON(t, "POST", hooksURL, api.CreateHookOption{
|
||||
Type: "gitea",
|
||||
Config: api.CreateHookOptionConfig{
|
||||
"content_type": "json",
|
||||
"url": "http://example.com/",
|
||||
},
|
||||
}).AddTokenAuth(token)
|
||||
resp2 := MakeRequest(t, req2, http.StatusCreated)
|
||||
var created *api.Hook
|
||||
DecodeJSON(t, resp2, &created)
|
||||
assert.Empty(t, created.Name)
|
||||
|
||||
hookURL := fmt.Sprintf("/api/v1/repos/%s/%s/hooks/%d", owner.Name, repo.Name, created.ID)
|
||||
|
||||
// PATCH with Name set: existing Name must be updated
|
||||
setName := "original"
|
||||
setReq := NewRequestWithJSON(t, "PATCH", hookURL, api.EditHookOption{
|
||||
Name: &setName,
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, setReq, http.StatusOK)
|
||||
|
||||
// PATCH without Name field: name must remain "original"
|
||||
patchReq2 := NewRequestWithJSON(t, "PATCH", hookURL, api.EditHookOption{}).AddTokenAuth(token)
|
||||
patchResp2 := MakeRequest(t, patchReq2, http.StatusOK)
|
||||
var notCleared *api.Hook
|
||||
DecodeJSON(t, patchResp2, ¬Cleared)
|
||||
assert.Equal(t, "original", notCleared.Name)
|
||||
|
||||
// PATCH with Name: "" explicitly: Name should be cleared to ""
|
||||
clearReq := NewRequestWithJSON(t, "PATCH", hookURL, api.EditHookOption{
|
||||
Name: new(""),
|
||||
}).AddTokenAuth(token)
|
||||
clearResp := MakeRequest(t, clearReq, http.StatusOK)
|
||||
var cleared *api.Hook
|
||||
DecodeJSON(t, clearResp, &cleared)
|
||||
assert.Empty(t, cleared.Name)
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ func TestRepoLanguages(t *testing.T) {
|
||||
|
||||
// Save new file to master branch
|
||||
req = NewRequestWithValues(t, "POST", "/user2/repo1/_new/master/", map[string]string{
|
||||
"_csrf": doc.GetCSRF(),
|
||||
"last_commit": lastCommit,
|
||||
"tree_path": "test.go",
|
||||
"content": "package main",
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAPILFSNotStarted(t *testing.T) {
|
||||
@@ -59,12 +60,12 @@ func TestAPILFSMediaType(t *testing.T) {
|
||||
MakeRequest(t, req, http.StatusUnsupportedMediaType)
|
||||
}
|
||||
|
||||
func createLFSTestRepository(t *testing.T, name string) *repo_model.Repository {
|
||||
ctx := NewAPITestContext(t, "user2", "lfs-"+name+"-repo", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
func createLFSTestRepository(t *testing.T, repoName string) *repo_model.Repository {
|
||||
ctx := NewAPITestContext(t, "user2", repoName, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreateRepo", doAPICreateRepository(ctx, false))
|
||||
|
||||
repo, err := repo_model.GetRepositoryByOwnerAndName(t.Context(), "user2", "lfs-"+name+"-repo")
|
||||
assert.NoError(t, err)
|
||||
repo, err := repo_model.GetRepositoryByOwnerAndName(t.Context(), "user2", repoName)
|
||||
require.NoError(t, err)
|
||||
|
||||
return repo
|
||||
}
|
||||
@@ -74,7 +75,7 @@ func TestAPILFSBatch(t *testing.T) {
|
||||
|
||||
setting.LFS.StartServer = true
|
||||
|
||||
repo := createLFSTestRepository(t, "batch")
|
||||
repo := createLFSTestRepository(t, "lfs-batch-repo")
|
||||
|
||||
content := []byte("dummy1")
|
||||
oid := storeObjectInRepo(t, repo.ID, &content)
|
||||
@@ -253,7 +254,7 @@ func TestAPILFSBatch(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist)
|
||||
|
||||
repo2 := createLFSTestRepository(t, "batch2")
|
||||
repo2 := createLFSTestRepository(t, "lfs-batch2-repo")
|
||||
content := []byte("dummy0")
|
||||
storeObjectInRepo(t, repo2.ID, &content)
|
||||
|
||||
@@ -316,6 +317,7 @@ func TestAPILFSBatch(t *testing.T) {
|
||||
ul := br.Objects[0].Actions["upload"]
|
||||
assert.NotNil(t, ul)
|
||||
assert.NotEmpty(t, ul.Href)
|
||||
assert.Equal(t, "chunked", ul.Header["Transfer-Encoding"], "git-lfs client needs Transfer-Encoding to do chunked transfer")
|
||||
assert.Contains(t, br.Objects[0].Actions, "verify")
|
||||
vl := br.Objects[0].Actions["verify"]
|
||||
assert.NotNil(t, vl)
|
||||
@@ -329,7 +331,7 @@ func TestAPILFSUpload(t *testing.T) {
|
||||
|
||||
setting.LFS.StartServer = true
|
||||
|
||||
repo := createLFSTestRepository(t, "upload")
|
||||
repo := createLFSTestRepository(t, "lfs-upload-repo")
|
||||
|
||||
content := []byte("dummy3")
|
||||
oid := storeObjectInRepo(t, repo.ID, &content)
|
||||
@@ -433,7 +435,7 @@ func TestAPILFSVerify(t *testing.T) {
|
||||
|
||||
setting.LFS.StartServer = true
|
||||
|
||||
repo := createLFSTestRepository(t, "verify")
|
||||
repo := createLFSTestRepository(t, "lfs-verify-repo")
|
||||
|
||||
content := []byte("dummy3")
|
||||
oid := storeObjectInRepo(t, repo.ID, &content)
|
||||
|
||||
@@ -43,7 +43,6 @@ func TestAPIRepoLicense(t *testing.T) {
|
||||
|
||||
// Save new file to master branch
|
||||
req = NewRequestWithValues(t, "POST", "/user2/repo1/_new/master/", map[string]string{
|
||||
"_csrf": doc.GetCSRF(),
|
||||
"last_commit": lastCommit,
|
||||
"tree_path": "LICENSE",
|
||||
"content": testLicenseContent,
|
||||
|
||||
@@ -671,6 +671,16 @@ func TestAPIGenerateRepo(t *testing.T) {
|
||||
|
||||
templateRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 44})
|
||||
|
||||
assertGeneratedRepoIsUsable := func(t *testing.T, ownerName string, repo *api.Repository) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, repo.DefaultBranch)
|
||||
|
||||
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/branches/%s", ownerName, repo.Name, repo.DefaultBranch).AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
branch := DecodeJSON(t, resp, &api.Branch{})
|
||||
assert.Equal(t, repo.DefaultBranch, branch.Name)
|
||||
}
|
||||
|
||||
// user
|
||||
repo := new(api.Repository)
|
||||
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate", templateRepo.OwnerName, templateRepo.Name), &api.GenerateRepoOption{
|
||||
@@ -684,6 +694,7 @@ func TestAPIGenerateRepo(t *testing.T) {
|
||||
DecodeJSON(t, resp, repo)
|
||||
|
||||
assert.Equal(t, "new-repo", repo.Name)
|
||||
assertGeneratedRepoIsUsable(t, user.Name, repo)
|
||||
|
||||
// org
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate", templateRepo.OwnerName, templateRepo.Name), &api.GenerateRepoOption{
|
||||
@@ -697,6 +708,7 @@ func TestAPIGenerateRepo(t *testing.T) {
|
||||
DecodeJSON(t, resp, repo)
|
||||
|
||||
assert.Equal(t, "new-repo", repo.Name)
|
||||
assertGeneratedRepoIsUsable(t, "org3", repo)
|
||||
}
|
||||
|
||||
func TestAPIRepoGetReviewers(t *testing.T) {
|
||||
|
||||
@@ -502,11 +502,12 @@ func runTestCase(t *testing.T, testCase *requiredScopeTestCase, user *user_model
|
||||
}
|
||||
unauthorizedLevel := auth_model.Write
|
||||
if categoryIsRequired {
|
||||
if minRequiredLevel == auth_model.Read {
|
||||
switch minRequiredLevel {
|
||||
case auth_model.Read:
|
||||
unauthorizedLevel = auth_model.NoAccess
|
||||
} else if minRequiredLevel == auth_model.Write {
|
||||
case auth_model.Write:
|
||||
unauthorizedLevel = auth_model.Read
|
||||
} else {
|
||||
default:
|
||||
assert.FailNow(t, "Invalid test case", "Unknown access token scope level: %v", minRequiredLevel)
|
||||
}
|
||||
}
|
||||
@@ -514,10 +515,10 @@ func runTestCase(t *testing.T, testCase *requiredScopeTestCase, user *user_model
|
||||
if unauthorizedLevel == auth_model.NoAccess {
|
||||
continue
|
||||
}
|
||||
cateogoryUnauthorizedScopes := auth_model.GetRequiredScopes(
|
||||
categoryUnauthorizedScopes := auth_model.GetRequiredScopes(
|
||||
unauthorizedLevel,
|
||||
category)
|
||||
unauthorizedScopes = append(unauthorizedScopes, cateogoryUnauthorizedScopes...)
|
||||
unauthorizedScopes = append(unauthorizedScopes, categoryUnauthorizedScopes...)
|
||||
}
|
||||
|
||||
accessToken := createAPIAccessTokenWithoutCleanUp(t, "test-token", user, unauthorizedScopes)
|
||||
|
||||
@@ -15,6 +15,21 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPermissionsAPI(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
t.Run("TokenNeeded", testTokenNeeded)
|
||||
t.Run("WithOwnerUser", testWithOwnerUser)
|
||||
t.Run("CanWriteUser", testCanWriteUser)
|
||||
t.Run("AdminUser", testAdminUser)
|
||||
t.Run("AdminCanNotCreateRepo", testAdminCanNotCreateRepo)
|
||||
t.Run("CanReadUser", testCanReadUser)
|
||||
t.Run("UnknownUser", testUnknownUser)
|
||||
t.Run("UnknownOrganization", testUnknownOrganization)
|
||||
t.Run("HiddenMemberPermissionsForbidden", testHiddenMemberPermissionsForbidden)
|
||||
t.Run("PrivateOrgPermissionsNotFound", testPrivateOrgPermissionsNotFound)
|
||||
}
|
||||
|
||||
type apiUserOrgPermTestCase struct {
|
||||
LoginUser string
|
||||
User string
|
||||
@@ -22,16 +37,12 @@ type apiUserOrgPermTestCase struct {
|
||||
ExpectedOrganizationPermissions api.OrganizationPermissions
|
||||
}
|
||||
|
||||
func TestTokenNeeded(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
func testTokenNeeded(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/api/v1/users/user1/orgs/org6/permissions")
|
||||
MakeRequest(t, req, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
func sampleTest(t *testing.T, auoptc apiUserOrgPermTestCase) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
session := loginUser(t, auoptc.LoginUser)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopeReadUser)
|
||||
|
||||
@@ -48,7 +59,7 @@ func sampleTest(t *testing.T, auoptc apiUserOrgPermTestCase) {
|
||||
assert.Equal(t, auoptc.ExpectedOrganizationPermissions.CanCreateRepository, apiOP.CanCreateRepository)
|
||||
}
|
||||
|
||||
func TestWithOwnerUser(t *testing.T) {
|
||||
func testWithOwnerUser(t *testing.T) {
|
||||
sampleTest(t, apiUserOrgPermTestCase{
|
||||
LoginUser: "user2",
|
||||
User: "user2",
|
||||
@@ -63,7 +74,7 @@ func TestWithOwnerUser(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestCanWriteUser(t *testing.T) {
|
||||
func testCanWriteUser(t *testing.T) {
|
||||
sampleTest(t, apiUserOrgPermTestCase{
|
||||
LoginUser: "user4",
|
||||
User: "user4",
|
||||
@@ -78,7 +89,7 @@ func TestCanWriteUser(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAdminUser(t *testing.T) {
|
||||
func testAdminUser(t *testing.T) {
|
||||
sampleTest(t, apiUserOrgPermTestCase{
|
||||
LoginUser: "user1",
|
||||
User: "user28",
|
||||
@@ -93,7 +104,7 @@ func TestAdminUser(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAdminCanNotCreateRepo(t *testing.T) {
|
||||
func testAdminCanNotCreateRepo(t *testing.T) {
|
||||
sampleTest(t, apiUserOrgPermTestCase{
|
||||
LoginUser: "user1",
|
||||
User: "user28",
|
||||
@@ -108,7 +119,7 @@ func TestAdminCanNotCreateRepo(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestCanReadUser(t *testing.T) {
|
||||
func testCanReadUser(t *testing.T) {
|
||||
sampleTest(t, apiUserOrgPermTestCase{
|
||||
LoginUser: "user1",
|
||||
User: "user24",
|
||||
@@ -123,31 +134,62 @@ func TestCanReadUser(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnknowUser(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
func testUnknownUser(t *testing.T) {
|
||||
session := loginUser(t, "user1")
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadOrganization)
|
||||
|
||||
req := NewRequest(t, "GET", "/api/v1/users/unknow/orgs/org25/permissions").
|
||||
req := NewRequest(t, "GET", "/api/v1/users/unknown/orgs/org25/permissions").
|
||||
AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
var apiError api.APIError
|
||||
DecodeJSON(t, resp, &apiError)
|
||||
assert.Equal(t, "user redirect does not exist [name: unknow]", apiError.Message)
|
||||
assert.Equal(t, "user redirect does not exist [name: unknown]", apiError.Message)
|
||||
}
|
||||
|
||||
func TestUnknowOrganization(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
func testUnknownOrganization(t *testing.T) {
|
||||
session := loginUser(t, "user1")
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadOrganization)
|
||||
|
||||
req := NewRequest(t, "GET", "/api/v1/users/user1/orgs/unknow/permissions").
|
||||
req := NewRequest(t, "GET", "/api/v1/users/user1/orgs/unknown/permissions").
|
||||
AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusNotFound)
|
||||
var apiError api.APIError
|
||||
DecodeJSON(t, resp, &apiError)
|
||||
assert.Equal(t, "GetUserByName", apiError.Message)
|
||||
}
|
||||
|
||||
func testHiddenMemberPermissionsForbidden(t *testing.T) {
|
||||
session := loginUser(t, "user8")
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadOrganization)
|
||||
|
||||
req := NewRequest(t, "GET", "/api/v1/users/user5/orgs/privated_org/permissions").
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
adminSession := loginUser(t, "user1")
|
||||
adminToken := getTokenForLoggedInUser(t, adminSession, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadOrganization)
|
||||
|
||||
adminReq := NewRequest(t, "GET", "/api/v1/users/user5/orgs/privated_org/permissions").
|
||||
AddTokenAuth(adminToken)
|
||||
resp := MakeRequest(t, adminReq, http.StatusOK)
|
||||
|
||||
var apiOP api.OrganizationPermissions
|
||||
DecodeJSON(t, resp, &apiOP)
|
||||
assert.Equal(t, api.OrganizationPermissions{
|
||||
IsOwner: false,
|
||||
IsAdmin: false,
|
||||
CanWrite: true,
|
||||
CanRead: true,
|
||||
CanCreateRepository: true,
|
||||
}, apiOP)
|
||||
}
|
||||
|
||||
func testPrivateOrgPermissionsNotFound(t *testing.T) {
|
||||
session := loginUser(t, "user8")
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadOrganization)
|
||||
|
||||
req := NewRequest(t, "GET", "/api/v1/users/user5/orgs/privated_org/permissions").
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
}
|
||||
|
||||
@@ -155,3 +155,44 @@ func TestMyOrgs(t *testing.T) {
|
||||
},
|
||||
}, orgs)
|
||||
}
|
||||
|
||||
func TestMyOrgsPublicOnly(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
normalUsername := "user2"
|
||||
token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopePublicOnly)
|
||||
req := NewRequest(t, "GET", "/api/v1/user/orgs").
|
||||
AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
var orgs []*api.Organization
|
||||
DecodeJSON(t, resp, &orgs)
|
||||
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"})
|
||||
org17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org17"})
|
||||
|
||||
assert.Equal(t, []*api.Organization{
|
||||
{
|
||||
ID: 17,
|
||||
Name: org17.Name,
|
||||
UserName: org17.Name,
|
||||
FullName: org17.FullName,
|
||||
Email: org17.Email,
|
||||
AvatarURL: org17.AvatarLink(t.Context()),
|
||||
Description: "",
|
||||
Website: "",
|
||||
Location: "",
|
||||
Visibility: "public",
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
Name: org3.Name,
|
||||
UserName: org3.Name,
|
||||
FullName: org3.FullName,
|
||||
Email: org3.Email,
|
||||
AvatarURL: org3.AvatarLink(t.Context()),
|
||||
Description: "",
|
||||
Website: "",
|
||||
Location: "",
|
||||
Visibility: "public",
|
||||
},
|
||||
}, orgs)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAPIPublicOnlySelfUserRoutes(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user31"})
|
||||
require.True(t, privateUser.Visibility.IsPrivate())
|
||||
|
||||
privateSession := loginUser(t, privateUser.Name)
|
||||
privateReadUserToken := getTokenForLoggedInUser(t, privateSession,
|
||||
auth_model.AccessTokenScopePublicOnly,
|
||||
auth_model.AccessTokenScopeReadUser,
|
||||
)
|
||||
privateWriteUserToken := getTokenForLoggedInUser(t, privateSession,
|
||||
auth_model.AccessTokenScopePublicOnly,
|
||||
auth_model.AccessTokenScopeWriteUser,
|
||||
)
|
||||
|
||||
t.Run("PrivateProfileForbidden", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/users/user31").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("PrivateSensitiveSelfRoutesForbidden", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/settings").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
hideEmail := true
|
||||
settingsReq := NewRequestWithJSON(t, "PATCH", "/api/v1/user/settings", &api.UserSettingsOptions{
|
||||
HideEmail: &hideEmail,
|
||||
}).AddTokenAuth(privateWriteUserToken)
|
||||
MakeRequest(t, settingsReq, http.StatusForbidden)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/emails").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
emailReq := NewRequestWithJSON(t, "POST", "/api/v1/user/emails", &api.CreateEmailOption{
|
||||
Emails: []string{"user31-public-only@example.com"},
|
||||
}).AddTokenAuth(privateWriteUserToken)
|
||||
MakeRequest(t, emailReq, http.StatusForbidden)
|
||||
|
||||
keyReq := NewRequestWithJSON(t, "POST", "/api/v1/user/keys", api.CreateKeyOption{
|
||||
Title: "public-only-private-key",
|
||||
Key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment",
|
||||
}).AddTokenAuth(privateWriteUserToken)
|
||||
MakeRequest(t, keyReq, http.StatusForbidden)
|
||||
|
||||
oauthReq := NewRequestWithJSON(t, "POST", "/api/v1/user/applications/oauth2", &api.CreateOAuth2ApplicationOptions{
|
||||
Name: "public-only-private-oauth-app",
|
||||
RedirectURIs: []string{"https://example.com/callback"},
|
||||
ConfidentialClient: true,
|
||||
}).AddTokenAuth(privateWriteUserToken)
|
||||
MakeRequest(t, oauthReq, http.StatusForbidden)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/gpg_keys").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
gpgKeyReq := NewRequestWithJSON(t, "POST", "/api/v1/user/gpg_keys", &api.CreateGPGKeyOption{
|
||||
ArmoredKey: "-----BEGIN PGP PUBLIC KEY BLOCK-----\ncomment\n-----END PGP PUBLIC KEY BLOCK-----",
|
||||
}).AddTokenAuth(privateWriteUserToken)
|
||||
MakeRequest(t, gpgKeyReq, http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/gpg_key_token").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
gpgVerifyReq := NewRequestWithJSON(t, "POST", "/api/v1/user/gpg_key_verify", &api.VerifyGPGKeyOption{
|
||||
KeyID: "deadbeef",
|
||||
Signature: "invalid-signature",
|
||||
}).AddTokenAuth(privateWriteUserToken)
|
||||
MakeRequest(t, gpgVerifyReq, http.StatusForbidden)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/actions/variables").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "DELETE", "/api/v1/user/actions/secrets/PRIVATE_SECRET").AddTokenAuth(privateWriteUserToken), http.StatusForbidden)
|
||||
variableReq := NewRequestWithJSON(t, "POST", "/api/v1/user/actions/variables/PRIVATE_VAR", api.CreateVariableOption{
|
||||
Value: "private-value",
|
||||
Description: "must stay private",
|
||||
}).AddTokenAuth(privateWriteUserToken)
|
||||
MakeRequest(t, variableReq, http.StatusForbidden)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "POST", "/api/v1/user/actions/runners/registration-token").AddTokenAuth(privateWriteUserToken), http.StatusForbidden)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/hooks").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
hookReq := NewRequestWithJSON(t, "POST", "/api/v1/user/hooks", api.CreateHookOption{
|
||||
Type: "gitea",
|
||||
Config: api.CreateHookOptionConfig{
|
||||
"content_type": "json",
|
||||
"url": "http://example.com/",
|
||||
},
|
||||
Name: "public-only-private-hook",
|
||||
}).AddTokenAuth(privateWriteUserToken)
|
||||
MakeRequest(t, hookReq, http.StatusForbidden)
|
||||
|
||||
avatarReq := NewRequestWithJSON(t, "POST", "/api/v1/user/avatar", &api.UpdateUserAvatarOption{
|
||||
Image: "aGVsbG8=",
|
||||
}).AddTokenAuth(privateWriteUserToken)
|
||||
MakeRequest(t, avatarReq, http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "DELETE", "/api/v1/user/avatar").AddTokenAuth(privateWriteUserToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/times").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/stopwatches").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/subscriptions").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/teams").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/blocks").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "PUT", "/api/v1/user/blocks/user2").AddTokenAuth(privateWriteUserToken), http.StatusForbidden)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "PUT", "/api/v1/user/following/user2").AddTokenAuth(privateWriteUserToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "DELETE", "/api/v1/user/following/user2").AddTokenAuth(privateWriteUserToken), http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("PublicRepoRoutesFilterAndRejectMutations", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
publicSession := loginUser(t, "user2")
|
||||
fullWriteRepoToken := getTokenForLoggedInUser(t, publicSession,
|
||||
auth_model.AccessTokenScopeWriteUser,
|
||||
auth_model.AccessTokenScopeWriteRepository,
|
||||
)
|
||||
publicOnlyReadRepoToken := getTokenForLoggedInUser(t, publicSession,
|
||||
auth_model.AccessTokenScopePublicOnly,
|
||||
auth_model.AccessTokenScopeReadUser,
|
||||
auth_model.AccessTokenScopeReadRepository,
|
||||
)
|
||||
publicOnlyWriteRepoToken := getTokenForLoggedInUser(t, publicSession,
|
||||
auth_model.AccessTokenScopePublicOnly,
|
||||
auth_model.AccessTokenScopeWriteUser,
|
||||
auth_model.AccessTokenScopeWriteRepository,
|
||||
)
|
||||
|
||||
publicRepoName := "public-only-visible-self-repo"
|
||||
privateRepoName := "public-only-hidden-self-repo"
|
||||
|
||||
resp := MakeRequest(t, NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{
|
||||
Name: publicRepoName,
|
||||
Private: false,
|
||||
}).AddTokenAuth(fullWriteRepoToken), http.StatusCreated)
|
||||
publicRepo := DecodeJSON(t, resp, &api.Repository{})
|
||||
require.Equal(t, "user2/"+publicRepoName, publicRepo.FullName)
|
||||
|
||||
resp = MakeRequest(t, NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{
|
||||
Name: privateRepoName,
|
||||
Private: true,
|
||||
}).AddTokenAuth(fullWriteRepoToken), http.StatusCreated)
|
||||
privateRepo := DecodeJSON(t, resp, &api.Repository{})
|
||||
require.Equal(t, "user2/"+privateRepoName, privateRepo.FullName)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/repos/user2/"+privateRepoName).AddTokenAuth(publicOnlyReadRepoToken), http.StatusNotFound)
|
||||
|
||||
resp = MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/repos").AddTokenAuth(publicOnlyReadRepoToken), http.StatusOK)
|
||||
repos := DecodeJSON(t, resp, []api.Repository{})
|
||||
|
||||
foundPublicRepo := false
|
||||
for _, repo := range repos {
|
||||
require.NotEqual(t, privateRepo.FullName, repo.FullName)
|
||||
if repo.FullName == publicRepo.FullName {
|
||||
foundPublicRepo = true
|
||||
}
|
||||
}
|
||||
require.True(t, foundPublicRepo)
|
||||
|
||||
MakeRequest(t, NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{
|
||||
Name: "public-only-rejected-self-repo",
|
||||
Private: false,
|
||||
}).AddTokenAuth(publicOnlyWriteRepoToken), http.StatusForbidden)
|
||||
})
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAPIStar(t *testing.T) {
|
||||
@@ -155,3 +156,24 @@ func TestAPIStarDisabled(t *testing.T) {
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIStarPublicOnly(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly)
|
||||
req := NewRequest(t, "GET", "/api/v1/user/starred").
|
||||
AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
repos := DecodeJSON(t, resp, []api.Repository{})
|
||||
if assert.Len(t, repos, 1) {
|
||||
assert.Equal(t, "user5/repo4", repos[0].FullName)
|
||||
}
|
||||
|
||||
req = NewRequest(t, "GET", "/api/v1/users/user2/starred").
|
||||
AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
repos = DecodeJSON(t, resp, []api.Repository{})
|
||||
require.Len(t, repos, 1)
|
||||
assert.Equal(t, "user5/repo4", repos[0].FullName)
|
||||
}
|
||||
|
||||
@@ -94,3 +94,28 @@ func TestAPIWatch(t *testing.T) {
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIWatchPublicOnly(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
session := loginUser(t, "user1")
|
||||
writeRepoToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeReadUser)
|
||||
publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopePublicOnly, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "PUT", "/api/v1/repos/user2/repo1/subscription").AddTokenAuth(writeRepoToken), http.StatusOK)
|
||||
MakeRequest(t, NewRequest(t, "PUT", "/api/v1/repos/user2/repo2/subscription").AddTokenAuth(writeRepoToken), http.StatusOK)
|
||||
|
||||
resp := MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/subscriptions").AddTokenAuth(publicOnlyToken), http.StatusOK)
|
||||
repos := DecodeJSON(t, resp, []api.Repository{})
|
||||
for _, r := range repos {
|
||||
assert.False(t, r.Private, "private repo %s leaked via /user/subscriptions", r.FullName)
|
||||
}
|
||||
assert.NotContains(t, repoNames(repos), "user2/repo2")
|
||||
|
||||
resp = MakeRequest(t, NewRequest(t, "GET", "/api/v1/users/user1/subscriptions").AddTokenAuth(publicOnlyToken), http.StatusOK)
|
||||
repos = DecodeJSON(t, resp, []api.Repository{})
|
||||
for _, r := range repos {
|
||||
assert.False(t, r.Private, "private repo %s leaked via /users/{username}/subscriptions", r.FullName)
|
||||
}
|
||||
assert.NotContains(t, repoNames(repos), "user2/repo2")
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
@@ -26,6 +27,15 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type attachmentScopeCase struct {
|
||||
name string
|
||||
url string
|
||||
readIssueStatus int
|
||||
readRepoStatus int
|
||||
publicOnlyIssueStatus int
|
||||
publicOnlyRepoStatus int
|
||||
}
|
||||
|
||||
func testGeneratePngBytes() []byte {
|
||||
myImage := image.NewRGBA(image.Rect(0, 0, 32, 32))
|
||||
var buff bytes.Buffer
|
||||
@@ -33,15 +43,15 @@ func testGeneratePngBytes() []byte {
|
||||
return buff.Bytes()
|
||||
}
|
||||
|
||||
func testCreateIssueAttachment(t *testing.T, session *TestSession, csrf, repoURL, filename string, content []byte, expectedStatus int) string {
|
||||
return testCreateAttachment(t, session, csrf, repoURL, "issues", filename, content, expectedStatus)
|
||||
func testCreateIssueAttachment(t *testing.T, session *TestSession, repoURL, filename string, content []byte, expectedStatus int) string {
|
||||
return testCreateAttachment(t, session, repoURL, "issues", filename, content, expectedStatus)
|
||||
}
|
||||
|
||||
func testCreateReleaseAttachment(t *testing.T, session *TestSession, csrf, repoURL, filename string, content []byte, expectedStatus int) string {
|
||||
return testCreateAttachment(t, session, csrf, repoURL, "releases", filename, content, expectedStatus)
|
||||
func testCreateReleaseAttachment(t *testing.T, session *TestSession, repoURL, filename string, content []byte, expectedStatus int) string {
|
||||
return testCreateAttachment(t, session, repoURL, "releases", filename, content, expectedStatus)
|
||||
}
|
||||
|
||||
func testCreateAttachment(t *testing.T, session *TestSession, csrf, repoURL, issueOrRelease, filename string, content []byte, expectedStatus int) string {
|
||||
func testCreateAttachment(t *testing.T, session *TestSession, repoURL, issueOrRelease, filename string, content []byte, expectedStatus int) string {
|
||||
body := &bytes.Buffer{}
|
||||
|
||||
// Setup multi-part
|
||||
@@ -54,7 +64,6 @@ func testCreateAttachment(t *testing.T, session *TestSession, csrf, repoURL, iss
|
||||
assert.NoError(t, err)
|
||||
|
||||
req := NewRequestWithBody(t, "POST", repoURL+"/"+issueOrRelease+"/attachments", body)
|
||||
req.Header.Add("X-Csrf-Token", csrf)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
resp := session.MakeRequest(t, req, expectedStatus)
|
||||
|
||||
@@ -66,15 +75,13 @@ func testCreateAttachment(t *testing.T, session *TestSession, csrf, repoURL, iss
|
||||
return obj["uuid"]
|
||||
}
|
||||
|
||||
func testDeleteIssueAttachment(t *testing.T, session *TestSession, csrf, repoURL, uuid string, expectedStatus int) {
|
||||
func testDeleteIssueAttachment(t *testing.T, session *TestSession, repoURL, uuid string, expectedStatus int) {
|
||||
req := NewRequestWithValues(t, "POST", repoURL+"/issues/attachments/remove", map[string]string{"file": uuid})
|
||||
req.Header.Add("X-Csrf-Token", csrf)
|
||||
session.MakeRequest(t, req, expectedStatus)
|
||||
}
|
||||
|
||||
func testDeleteReleaseAttachment(t *testing.T, session *TestSession, csrf, repoURL, uuid string, expectedStatus int) {
|
||||
func testDeleteReleaseAttachment(t *testing.T, session *TestSession, repoURL, uuid string, expectedStatus int) {
|
||||
req := NewRequestWithValues(t, "POST", repoURL+"/releases/attachments/remove", map[string]string{"file": uuid})
|
||||
req.Header.Add("X-Csrf-Token", csrf)
|
||||
session.MakeRequest(t, req, expectedStatus)
|
||||
}
|
||||
|
||||
@@ -100,20 +107,20 @@ func testUploadAttachmentDeleteTemp(t *testing.T) {
|
||||
defer web.RouteMock(route_web.RouterMockPointBeforeWebRoutes, func(resp http.ResponseWriter, req *http.Request) {
|
||||
tmpFileCountDuringUpload = countTmpFile()
|
||||
})()
|
||||
_ = testCreateIssueAttachment(t, session, GetUserCSRFToken(t, session), "user2/repo1", "image.png", testGeneratePngBytes(), http.StatusOK)
|
||||
_ = testCreateIssueAttachment(t, session, "user2/repo1", "image.png", testGeneratePngBytes(), http.StatusOK)
|
||||
assert.Equal(t, 1, tmpFileCountDuringUpload, "the temp file should exist when uploaded size exceeds the parse form's max memory")
|
||||
assert.Equal(t, 0, countTmpFile(), "the temp file should be deleted after upload")
|
||||
}
|
||||
|
||||
func testCreateAnonymousAttachment(t *testing.T) {
|
||||
session := emptyTestSession(t)
|
||||
testCreateIssueAttachment(t, session, GetAnonymousCSRFToken(t, session), "user2/repo1", "image.png", testGeneratePngBytes(), http.StatusSeeOther)
|
||||
testCreateIssueAttachment(t, session, "user2/repo1", "image.png", testGeneratePngBytes(), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func testCreateUser2IssueAttachment(t *testing.T) {
|
||||
const repoURL = "user2/repo1"
|
||||
session := loginUser(t, "user2")
|
||||
uuid := testCreateIssueAttachment(t, session, GetUserCSRFToken(t, session), repoURL, "image.png", testGeneratePngBytes(), http.StatusOK)
|
||||
uuid := testCreateIssueAttachment(t, session, repoURL, "image.png", testGeneratePngBytes(), http.StatusOK)
|
||||
|
||||
req := NewRequest(t, "GET", repoURL+"/issues/new")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
@@ -123,7 +130,6 @@ func testCreateUser2IssueAttachment(t *testing.T) {
|
||||
assert.True(t, exists, "The template has changed")
|
||||
|
||||
postData := map[string]string{
|
||||
"_csrf": htmlDoc.GetCSRF(),
|
||||
"title": "New Issue With Attachment",
|
||||
"content": "some content",
|
||||
"files": uuid,
|
||||
@@ -187,24 +193,118 @@ func testDeleteAttachmentPermissions(t *testing.T) {
|
||||
ownerSession := loginUser(t, "user2")
|
||||
readonlySession := loginUser(t, "user5")
|
||||
|
||||
ownerCSRF := GetUserCSRFToken(t, ownerSession)
|
||||
readonlyCSRF := GetUserCSRFToken(t, readonlySession)
|
||||
issueFromOwner := testCreateIssueAttachment(t, ownerSession, repoURL, "owner-issue.png", testGeneratePngBytes(), http.StatusOK)
|
||||
testDeleteIssueAttachment(t, readonlySession, repoURL, issueFromOwner, http.StatusForbidden)
|
||||
|
||||
issueFromOwner := testCreateIssueAttachment(t, ownerSession, ownerCSRF, repoURL, "owner-issue.png", testGeneratePngBytes(), http.StatusOK)
|
||||
testDeleteIssueAttachment(t, readonlySession, readonlyCSRF, repoURL, issueFromOwner, http.StatusForbidden)
|
||||
issueFromReader := testCreateIssueAttachment(t, readonlySession, repoURL, "reader-issue.png", testGeneratePngBytes(), http.StatusOK)
|
||||
testDeleteIssueAttachment(t, ownerSession, repoURL, issueFromReader, http.StatusOK)
|
||||
|
||||
issueFromReader := testCreateIssueAttachment(t, readonlySession, readonlyCSRF, repoURL, "reader-issue.png", testGeneratePngBytes(), http.StatusOK)
|
||||
testDeleteIssueAttachment(t, ownerSession, ownerCSRF, repoURL, issueFromReader, http.StatusOK)
|
||||
testCreateReleaseAttachment(t, readonlySession, repoURL, "reader-release.png", testGeneratePngBytes(), http.StatusNotFound)
|
||||
|
||||
testCreateReleaseAttachment(t, readonlySession, readonlyCSRF, repoURL, "reader-release.png", testGeneratePngBytes(), http.StatusNotFound)
|
||||
crossRepoUUID := testCreateIssueAttachment(t, ownerSession, repoURL, "cross-repo.png", testGeneratePngBytes(), http.StatusOK)
|
||||
testDeleteIssueAttachment(t, ownerSession, "user2/repo2", crossRepoUUID, http.StatusBadRequest)
|
||||
testDeleteIssueAttachment(t, ownerSession, repoURL, crossRepoUUID, http.StatusOK)
|
||||
|
||||
crossRepoUUID := testCreateIssueAttachment(t, ownerSession, ownerCSRF, repoURL, "cross-repo.png", testGeneratePngBytes(), http.StatusOK)
|
||||
testDeleteIssueAttachment(t, ownerSession, ownerCSRF, "user2/repo2", crossRepoUUID, http.StatusBadRequest)
|
||||
testDeleteIssueAttachment(t, ownerSession, ownerCSRF, repoURL, crossRepoUUID, http.StatusOK)
|
||||
|
||||
releaseUUID := testCreateReleaseAttachment(t, ownerSession, ownerCSRF, repoURL, "reader-release.png", testGeneratePngBytes(), http.StatusOK)
|
||||
testDeleteReleaseAttachment(t, ownerSession, ownerCSRF, repoURL, releaseUUID, http.StatusOK)
|
||||
releaseUUID := testCreateReleaseAttachment(t, ownerSession, repoURL, "reader-release.png", testGeneratePngBytes(), http.StatusOK)
|
||||
testDeleteReleaseAttachment(t, ownerSession, repoURL, releaseUUID, http.StatusOK)
|
||||
|
||||
// test deleting release attachment from another repo
|
||||
testDeleteReleaseAttachment(t, ownerSession, ownerCSRF, "user2/repo2", crossRepoUUID, http.StatusBadRequest)
|
||||
testDeleteReleaseAttachment(t, ownerSession, "user2/repo2", crossRepoUUID, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func TestAttachmentTokenScopes(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
for _, uuid := range []string{
|
||||
"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
|
||||
"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12",
|
||||
"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a19",
|
||||
"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22",
|
||||
} {
|
||||
_, err := storage.Attachments.Save(repo_model.AttachmentRelativePath(uuid), strings.NewReader("hello world"), -1)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
readIssueToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadIssue)
|
||||
readRepoToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository)
|
||||
miscToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadMisc)
|
||||
publicOnlyIssueToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopePublicOnly)
|
||||
publicOnlyRepoToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly)
|
||||
|
||||
cases := []attachmentScopeCase{
|
||||
{
|
||||
name: "GlobalPublicIssueAttachment",
|
||||
url: "/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
|
||||
readIssueStatus: http.StatusOK,
|
||||
readRepoStatus: http.StatusForbidden,
|
||||
publicOnlyIssueStatus: http.StatusOK,
|
||||
publicOnlyRepoStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "RepoPublicIssueAttachment",
|
||||
url: "/user2/repo1/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
|
||||
readIssueStatus: http.StatusOK,
|
||||
readRepoStatus: http.StatusForbidden,
|
||||
publicOnlyIssueStatus: http.StatusOK,
|
||||
publicOnlyRepoStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "GlobalPrivateIssueAttachment",
|
||||
url: "/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12",
|
||||
readIssueStatus: http.StatusOK,
|
||||
readRepoStatus: http.StatusForbidden,
|
||||
publicOnlyIssueStatus: http.StatusForbidden,
|
||||
publicOnlyRepoStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "RepoPrivateIssueAttachment",
|
||||
url: "/user2/repo2/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12",
|
||||
readIssueStatus: http.StatusOK,
|
||||
readRepoStatus: http.StatusForbidden,
|
||||
publicOnlyIssueStatus: http.StatusForbidden,
|
||||
publicOnlyRepoStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "GlobalPublicReleaseAttachment",
|
||||
url: "/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a19",
|
||||
readIssueStatus: http.StatusForbidden,
|
||||
readRepoStatus: http.StatusOK,
|
||||
publicOnlyIssueStatus: http.StatusForbidden,
|
||||
publicOnlyRepoStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "RepoPublicReleaseAttachment",
|
||||
url: "/user2/repo1/releases/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a19",
|
||||
readIssueStatus: http.StatusForbidden,
|
||||
readRepoStatus: http.StatusOK,
|
||||
publicOnlyIssueStatus: http.StatusForbidden,
|
||||
publicOnlyRepoStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "GlobalPrivateReleaseAttachment",
|
||||
url: "/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22",
|
||||
readIssueStatus: http.StatusForbidden,
|
||||
readRepoStatus: http.StatusOK,
|
||||
publicOnlyIssueStatus: http.StatusForbidden,
|
||||
publicOnlyRepoStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "RepoPrivateReleaseAttachment",
|
||||
url: "/user2/repo2/releases/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22",
|
||||
readIssueStatus: http.StatusForbidden,
|
||||
readRepoStatus: http.StatusOK,
|
||||
publicOnlyIssueStatus: http.StatusForbidden,
|
||||
publicOnlyRepoStatus: http.StatusForbidden,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
MakeRequest(t, NewRequest(t, "GET", tc.url).AddTokenAuth(miscToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "GET", tc.url).AddTokenAuth(readIssueToken), tc.readIssueStatus)
|
||||
MakeRequest(t, NewRequest(t, "GET", tc.url).AddTokenAuth(readRepoToken), tc.readRepoStatus)
|
||||
MakeRequest(t, NewRequest(t, "GET", tc.url).AddTokenAuth(publicOnlyIssueToken), tc.publicOnlyIssueStatus)
|
||||
MakeRequest(t, NewRequest(t, "GET", tc.url).AddTokenAuth(publicOnlyRepoToken), tc.publicOnlyRepoStatus)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ type ldapAuthOptions struct {
|
||||
groupTeamMapRemoval string
|
||||
}
|
||||
|
||||
func (te *ldapTestEnv) buildAuthSourcePayload(csrf string, opts ...ldapAuthOptions) map[string]string {
|
||||
func (te *ldapTestEnv) buildAuthSourcePayload(opts ...ldapAuthOptions) map[string]string {
|
||||
opt := util.OptionalArg(opts)
|
||||
// Modify user filter to test group filter explicitly
|
||||
userFilter := "(&(objectClass=inetOrgPerson)(memberOf=cn=git,ou=people,dc=planetexpress,dc=com)(uid=%s))"
|
||||
@@ -134,7 +134,6 @@ func (te *ldapTestEnv) buildAuthSourcePayload(csrf string, opts ...ldapAuthOptio
|
||||
}
|
||||
|
||||
return map[string]string{
|
||||
"_csrf": csrf,
|
||||
"type": "2",
|
||||
"name": "ldap",
|
||||
"host": te.serverHost,
|
||||
@@ -164,8 +163,7 @@ func (te *ldapTestEnv) buildAuthSourcePayload(csrf string, opts ...ldapAuthOptio
|
||||
|
||||
func (te *ldapTestEnv) addAuthSource(t *testing.T, opts ...ldapAuthOptions) {
|
||||
session := loginUser(t, "user1")
|
||||
csrf := GetUserCSRFToken(t, session)
|
||||
req := NewRequestWithValues(t, "POST", "/-/admin/auths/new", te.buildAuthSourcePayload(csrf, opts...))
|
||||
req := NewRequestWithValues(t, "POST", "/-/admin/auths/new", te.buildAuthSourcePayload(opts...))
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
@@ -212,13 +210,12 @@ func TestLDAPAuthChange(t *testing.T) {
|
||||
req = NewRequest(t, "GET", href)
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
doc = NewHTMLParser(t, resp.Body)
|
||||
csrf := doc.GetCSRF()
|
||||
host, _ := doc.Find(`input[name="host"]`).Attr("value")
|
||||
assert.Equal(t, te.serverHost, host)
|
||||
binddn, _ := doc.Find(`input[name="bind_dn"]`).Attr("value")
|
||||
assert.Equal(t, "uid=gitea,ou=service,dc=planetexpress,dc=com", binddn)
|
||||
|
||||
req = NewRequestWithValues(t, "POST", href, te.buildAuthSourcePayload(csrf, ldapAuthOptions{groupTeamMapRemoval: "off"}))
|
||||
req = NewRequestWithValues(t, "POST", href, te.buildAuthSourcePayload(ldapAuthOptions{groupTeamMapRemoval: "off"}))
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
req = NewRequest(t, "GET", href)
|
||||
@@ -267,8 +264,7 @@ func TestLDAPUserSyncWithEmptyUsernameAttribute(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
session := loginUser(t, "user1")
|
||||
csrf := GetUserCSRFToken(t, session)
|
||||
payload := te.buildAuthSourcePayload(csrf)
|
||||
payload := te.buildAuthSourcePayload()
|
||||
payload["attribute_username"] = ""
|
||||
req := NewRequestWithValues(t, "POST", "/-/admin/auths/new", payload)
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
@@ -285,7 +281,6 @@ func TestLDAPUserSyncWithEmptyUsernameAttribute(t *testing.T) {
|
||||
|
||||
for _, u := range te.gitLDAPUsers {
|
||||
req := NewRequestWithValues(t, "POST", "/user/login", map[string]string{
|
||||
"_csrf": csrf,
|
||||
"user_name": u.UserName,
|
||||
"password": u.Password,
|
||||
})
|
||||
@@ -512,8 +507,7 @@ func TestLDAPPreventInvalidGroupTeamMap(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
session := loginUser(t, "user1")
|
||||
csrf := GetUserCSRFToken(t, session)
|
||||
payload := te.buildAuthSourcePayload(csrf, ldapAuthOptions{groupTeamMap: `{"NOT_A_VALID_JSON"["MISSING_DOUBLE_POINT"]}`, groupTeamMapRemoval: "off"})
|
||||
payload := te.buildAuthSourcePayload(ldapAuthOptions{groupTeamMap: `{"NOT_A_VALID_JSON"["MISSING_DOUBLE_POINT"]}`, groupTeamMapRemoval: "off"})
|
||||
req := NewRequestWithValues(t, "POST", "/-/admin/auths/new", payload)
|
||||
session.MakeRequest(t, req, http.StatusOK) // StatusOK = failed, StatusSeeOther = ok
|
||||
}
|
||||
|
||||
@@ -61,9 +61,7 @@ func branchAction(t *testing.T, button string) (*HTMLDoc, string) {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
req = NewRequestWithValues(t, "POST", link, map[string]string{
|
||||
"_csrf": htmlDoc.GetCSRF(),
|
||||
})
|
||||
req = NewRequest(t, "POST", link)
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
url, err := url.Parse(link)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -26,24 +26,20 @@ func TestChangeDefaultBranch(t *testing.T) {
|
||||
session := loginUser(t, owner.Name)
|
||||
branchesURL := fmt.Sprintf("/%s/%s/settings/branches", owner.Name, repo.Name)
|
||||
|
||||
csrf := GetUserCSRFToken(t, session)
|
||||
req := NewRequestWithValues(t, "POST", branchesURL, map[string]string{
|
||||
"_csrf": csrf,
|
||||
"action": "default_branch",
|
||||
"branch": "DefaultBranch",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
csrf = GetUserCSRFToken(t, session)
|
||||
req = NewRequestWithValues(t, "POST", branchesURL, map[string]string{
|
||||
"_csrf": csrf,
|
||||
"action": "default_branch",
|
||||
"branch": "does_not_exist",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func checkDivergence(t *testing.T, session *TestSession, branchesURL, expectedDefaultBranch string, expectedBranchToDivergence map[string]git.DivergeObject) {
|
||||
func checkDivergence(t *testing.T, session *TestSession, branchesURL, expectedDefaultBranch string, expectedBranchToDivergence map[string]*gitrepo.DivergeObject) {
|
||||
req := NewRequest(t, "GET", branchesURL)
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
@@ -92,7 +88,7 @@ func TestChangeDefaultBranchDivergence(t *testing.T) {
|
||||
settingsBranchesURL := fmt.Sprintf("/%s/%s/settings/branches", owner.Name, repo.Name)
|
||||
|
||||
// check branch divergence before switching default branch
|
||||
expectedBranchToDivergenceBefore := map[string]git.DivergeObject{
|
||||
expectedBranchToDivergenceBefore := map[string]*gitrepo.DivergeObject{
|
||||
"not-signed": {
|
||||
Ahead: 0,
|
||||
Behind: 0,
|
||||
@@ -110,16 +106,14 @@ func TestChangeDefaultBranchDivergence(t *testing.T) {
|
||||
|
||||
// switch default branch
|
||||
newDefaultBranch := "good-sign-not-yet-validated"
|
||||
csrf := GetUserCSRFToken(t, session)
|
||||
req := NewRequestWithValues(t, "POST", settingsBranchesURL, map[string]string{
|
||||
"_csrf": csrf,
|
||||
"action": "default_branch",
|
||||
"branch": newDefaultBranch,
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
// check branch divergence after switching default branch
|
||||
expectedBranchToDivergenceAfter := map[string]git.DivergeObject{
|
||||
expectedBranchToDivergenceAfter := map[string]*gitrepo.DivergeObject{
|
||||
"master": {
|
||||
Ahead: 1,
|
||||
Behind: 0,
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/cmd"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -38,13 +37,14 @@ func Test_CmdKeys(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// FIXME: this test is not quite right. Each "command run" always re-initializes settings
|
||||
defer test.MockVariableValue(&cmd.CmdKeys.Before, nil)() // don't re-initialize logger during the test
|
||||
keysCmd := cmd.NewKeysCommand()
|
||||
keysCmd.Before = nil // don't re-initialize logger during the test
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
app := &cli.Command{
|
||||
Writer: &stdout,
|
||||
ErrWriter: &stderr,
|
||||
Commands: []*cli.Command{cmd.CmdKeys},
|
||||
Commands: []*cli.Command{keysCmd},
|
||||
}
|
||||
err := app.Run(t.Context(), append([]string{"prog"}, tt.args...))
|
||||
if tt.wantErr {
|
||||
|
||||
@@ -4,19 +4,25 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCompareTag(t *testing.T) {
|
||||
@@ -124,6 +130,76 @@ func TestCompareBranches(t *testing.T) {
|
||||
inspectCompare(t, htmlDoc, diffCount, diffChanges)
|
||||
}
|
||||
|
||||
func createUnrelatedBranch(t *testing.T, repo *repo_model.Repository, user *user_model.User, branchName string) {
|
||||
t.Helper()
|
||||
|
||||
repoPath := repo_model.RepoPath(user.Name, repo.Name)
|
||||
require.NoError(t, gitcmd.NewCommand("read-tree", "--empty").WithDir(repoPath).Run(t.Context()))
|
||||
|
||||
stdout, _, err := gitcmd.NewCommand("hash-object", "-w", "--stdin").
|
||||
WithDir(repoPath).
|
||||
WithStdinBytes([]byte("Unrelated File")).
|
||||
RunStdString(t.Context())
|
||||
require.NoError(t, err)
|
||||
blobSHA := strings.TrimSpace(stdout)
|
||||
|
||||
_, _, err = gitcmd.NewCommand("update-index", "--add", "--replace", "--cacheinfo").
|
||||
AddDynamicArguments("100644", blobSHA, "unrelated.txt").
|
||||
WithDir(repoPath).
|
||||
RunStdString(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
stdout, _, err = gitcmd.NewCommand("write-tree").WithDir(repoPath).RunStdString(t.Context())
|
||||
require.NoError(t, err)
|
||||
treeSHA := strings.TrimSpace(stdout)
|
||||
|
||||
commitTimeStr := time.Now().Format(time.RFC3339)
|
||||
doerSig := user.NewGitSig()
|
||||
env := append(os.Environ(),
|
||||
"GIT_AUTHOR_NAME="+doerSig.Name,
|
||||
"GIT_AUTHOR_EMAIL="+doerSig.Email,
|
||||
"GIT_AUTHOR_DATE="+commitTimeStr,
|
||||
"GIT_COMMITTER_NAME="+doerSig.Name,
|
||||
"GIT_COMMITTER_EMAIL="+doerSig.Email,
|
||||
"GIT_COMMITTER_DATE="+commitTimeStr,
|
||||
)
|
||||
|
||||
messageBytes := bytes.NewBufferString("Unrelated\n")
|
||||
stdout, _, err = gitcmd.NewCommand("commit-tree").AddDynamicArguments(treeSHA).
|
||||
WithEnv(env).
|
||||
WithDir(repoPath).
|
||||
WithStdinBytes(messageBytes.Bytes()).
|
||||
RunStdString(t.Context())
|
||||
require.NoError(t, err)
|
||||
commitSHA := strings.TrimSpace(stdout)
|
||||
|
||||
_, _, err = gitcmd.NewCommand("branch").AddDynamicArguments(branchName, commitSHA).
|
||||
WithDir(repoPath).
|
||||
RunStdString(t.Context())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCompareBranchesNoCommonMergeBase(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"})
|
||||
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user2.ID, Name: "repo1"})
|
||||
createUnrelatedBranch(t, repo1, user2, "unrelated-history")
|
||||
|
||||
session := loginUser(t, "user2")
|
||||
req := NewRequest(t, "GET", "/user2/repo1/compare/master...unrelated-history")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
body := resp.Body.String()
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
|
||||
selection := htmlDoc.doc.Find(".ui.dropdown.select-branch")
|
||||
assert.Lenf(t, selection.Nodes, 2, "The template has changed")
|
||||
assert.Contains(t, body, "These branches do not share a common merge base")
|
||||
assert.Equal(t, 1, htmlDoc.doc.Find(`a.item[href="/user2/repo1/compare/master...unrelated-history"]`).Length())
|
||||
assert.Equal(t, 1, htmlDoc.doc.Find(`a.item[href="/user2/repo1/compare/master...master"]`).Length())
|
||||
assert.Equal(t, 0, htmlDoc.doc.Find(".pullrequest-form").Length())
|
||||
}
|
||||
|
||||
func TestCompareCodeExpand(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
system_model "code.gitea.io/gitea/models/system"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/setting/config"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func mockSystemConfig[T any](t *testing.T, opt *config.Option[T], v T) func() {
|
||||
jsonBuf, _ := json.Marshal(v)
|
||||
old := opt.Value(t.Context())
|
||||
require.NoError(t, system_model.SetSettings(t.Context(), map[string]string{opt.DynKey(): string(jsonBuf)}))
|
||||
config.GetDynGetter().InvalidateCache()
|
||||
return func() {
|
||||
jsonBuf, _ := json.Marshal(old)
|
||||
require.NoError(t, system_model.SetSettings(t.Context(), map[string]string{opt.DynKey(): string(jsonBuf)}))
|
||||
config.GetDynGetter().InvalidateCache()
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstance(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
t.Run("WebBanner", func(t *testing.T) {
|
||||
t.Run("Visibility", func(t *testing.T) {
|
||||
defer mockSystemConfig(t, setting.Config().Instance.WebBanner, setting.WebBannerType{
|
||||
DisplayEnabled: true,
|
||||
ContentMessage: "Planned **upgrade** in progress.",
|
||||
})()
|
||||
|
||||
t.Run("AnonymousUserSeesBanner", func(t *testing.T) {
|
||||
resp := MakeRequest(t, NewRequest(t, "GET", "/"), http.StatusOK)
|
||||
assert.Contains(t, resp.Body.String(), "Planned <strong>upgrade</strong> in progress.")
|
||||
})
|
||||
|
||||
t.Run("NormalUserSeesBanner", func(t *testing.T) {
|
||||
sess := loginUser(t, "user2")
|
||||
resp := sess.MakeRequest(t, NewRequest(t, "GET", "/user/settings"), http.StatusOK)
|
||||
assert.Contains(t, resp.Body.String(), "Planned <strong>upgrade</strong> in progress.")
|
||||
})
|
||||
|
||||
t.Run("AdminSeesBannerWithoutEditHint", func(t *testing.T) {
|
||||
sess := loginUser(t, "user1")
|
||||
resp := sess.MakeRequest(t, NewRequest(t, "GET", "/-/admin"), http.StatusOK)
|
||||
assert.Contains(t, resp.Body.String(), "Planned <strong>upgrade</strong> in progress.")
|
||||
assert.NotContains(t, resp.Body.String(), "Edit this banner")
|
||||
})
|
||||
|
||||
t.Run("APIRequestUnchanged", func(t *testing.T) {
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/version"), http.StatusOK)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("TimeWindow", func(t *testing.T) {
|
||||
now := time.Now().Unix()
|
||||
defer mockSystemConfig(t, setting.Config().Instance.WebBanner, setting.WebBannerType{
|
||||
DisplayEnabled: true,
|
||||
ContentMessage: "Future banner",
|
||||
StartTimeUnix: now + 3600,
|
||||
EndTimeUnix: now + 7200,
|
||||
})()
|
||||
|
||||
resp := MakeRequest(t, NewRequest(t, "GET", "/"), http.StatusOK)
|
||||
assert.NotContains(t, resp.Body.String(), "Future banner")
|
||||
|
||||
defer mockSystemConfig(t, setting.Config().Instance.WebBanner, setting.WebBannerType{
|
||||
DisplayEnabled: true,
|
||||
ContentMessage: "Expired banner",
|
||||
StartTimeUnix: now - 7200,
|
||||
EndTimeUnix: now - 3600,
|
||||
})()
|
||||
|
||||
resp = MakeRequest(t, NewRequest(t, "GET", "/"), http.StatusOK)
|
||||
assert.NotContains(t, resp.Body.String(), "Expired banner")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("MaintenanceMode", func(t *testing.T) {
|
||||
defer mockSystemConfig(t, setting.Config().Instance.WebBanner, setting.WebBannerType{
|
||||
DisplayEnabled: true,
|
||||
ContentMessage: "MaintenanceModeBanner",
|
||||
})()
|
||||
defer mockSystemConfig(t, setting.Config().Instance.MaintenanceMode, setting.MaintenanceModeType{AdminWebAccessOnly: true})()
|
||||
|
||||
t.Run("AnonymousUser", func(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/")
|
||||
req.Header.Add("Accept", "text/html")
|
||||
resp := MakeRequest(t, req, http.StatusServiceUnavailable)
|
||||
assert.Contains(t, resp.Body.String(), "MaintenanceModeBanner")
|
||||
assert.Contains(t, resp.Body.String(), `href="/user/login"`) // it must contain the login link
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", "/user/login"), http.StatusOK)
|
||||
MakeRequest(t, NewRequest(t, "GET", "/-/admin"), http.StatusSeeOther)
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/internal/dummy"), http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("AdminLogin", func(t *testing.T) {
|
||||
req := NewRequestWithValues(t, "POST", "/user/login", map[string]string{"user_name": "user1", "password": userPassword})
|
||||
resp := MakeRequest(t, req, http.StatusSeeOther)
|
||||
assert.Equal(t, "/-/admin", resp.Header().Get("Location"))
|
||||
|
||||
sess := loginUser(t, "user1")
|
||||
req = NewRequest(t, "GET", "/")
|
||||
req.Header.Add("Accept", "text/html")
|
||||
resp = sess.MakeRequest(t, req, http.StatusServiceUnavailable)
|
||||
assert.Contains(t, resp.Body.String(), "MaintenanceModeBanner")
|
||||
|
||||
resp = sess.MakeRequest(t, NewRequest(t, "GET", "/user/login"), http.StatusSeeOther)
|
||||
assert.Equal(t, "/-/admin", resp.Header().Get("Location"))
|
||||
|
||||
sess.MakeRequest(t, NewRequest(t, "GET", "/-/admin"), http.StatusOK)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -97,9 +97,7 @@ func TestSessionFileCreation(t *testing.T) {
|
||||
// We're not logged in so there should be no session
|
||||
assert.False(t, sessionFileExist(t, tmpDir, sessionID))
|
||||
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
req = NewRequestWithValues(t, "POST", "/user/login", map[string]string{
|
||||
"_csrf": doc.GetCSRF(),
|
||||
"user_name": "user2",
|
||||
"password": userPassword,
|
||||
})
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCsrfProtection(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
// test web form csrf via form
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
session := loginUser(t, user.Name)
|
||||
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
|
||||
"_csrf": "fake_csrf",
|
||||
})
|
||||
resp := session.MakeRequest(t, req, http.StatusBadRequest)
|
||||
assert.Contains(t, resp.Body.String(), "Invalid CSRF token")
|
||||
|
||||
// test web form csrf via header. TODO: should use an UI api to test
|
||||
req = NewRequest(t, "POST", "/user/settings")
|
||||
req.Header.Add("X-Csrf-Token", "fake_csrf")
|
||||
resp = session.MakeRequest(t, req, http.StatusBadRequest)
|
||||
assert.Contains(t, resp.Body.String(), "Invalid CSRF token")
|
||||
}
|
||||
@@ -32,11 +32,8 @@ func TestUserDeleteAccount(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
session := loginUser(t, "user8")
|
||||
csrf := GetUserCSRFToken(t, session)
|
||||
urlStr := "/user/settings/account/delete?password=" + userPassword
|
||||
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
|
||||
"_csrf": csrf,
|
||||
})
|
||||
req := NewRequest(t, "POST", urlStr)
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
assertUserDeleted(t, 8)
|
||||
@@ -47,11 +44,8 @@ func TestUserDeleteAccountStillOwnRepos(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
session := loginUser(t, "user2")
|
||||
csrf := GetUserCSRFToken(t, session)
|
||||
urlStr := "/user/settings/account/delete?password=" + userPassword
|
||||
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
|
||||
"_csrf": csrf,
|
||||
})
|
||||
req := NewRequest(t, "POST", urlStr)
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
// user should not have been deleted, because the user still owns repos
|
||||
|
||||
@@ -7,87 +7,205 @@ import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDownloadByID(t *testing.T) {
|
||||
type downloadScopeCase struct {
|
||||
name string
|
||||
method string
|
||||
url string
|
||||
withScope int
|
||||
publicOnlyOK bool
|
||||
}
|
||||
|
||||
func TestDownloadRepoContent(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
// Request raw blob
|
||||
req := NewRequest(t, "GET", "/user2/repo1/raw/blob/4b4851ad51df6a7d9f25c979345979eaeb5b349f")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
t.Run("RawBlob", func(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/user2/repo1/raw/blob/4b4851ad51df6a7d9f25c979345979eaeb5b349f")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
assert.Equal(t, "# repo1\n\nDescription for repo1", resp.Body.String())
|
||||
})
|
||||
|
||||
assert.Equal(t, "# repo1\n\nDescription for repo1", resp.Body.String())
|
||||
t.Run("SVGUsesSecureHeaders", func(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/user2/repo2/raw/blob/6395b68e1feebb1e4c657b4f9f6ba2676a283c0b")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
assert.Equal(t, "default-src 'none'; style-src 'unsafe-inline'; sandbox", resp.Header().Get("Content-Security-Policy"))
|
||||
assert.Equal(t, "image/svg+xml", resp.Header().Get("Content-Type"))
|
||||
assert.Equal(t, "nosniff", resp.Header().Get("X-Content-Type-Options"))
|
||||
})
|
||||
|
||||
t.Run("MediaBlob", func(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/user2/repo1/media/blob/4b4851ad51df6a7d9f25c979345979eaeb5b349f")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
assert.Equal(t, "# repo1\n\nDescription for repo1", resp.Body.String())
|
||||
})
|
||||
|
||||
t.Run("MediaSVGUsesSecureHeaders", func(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/user2/repo2/media/blob/6395b68e1feebb1e4c657b4f9f6ba2676a283c0b")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
assert.Equal(t, "default-src 'none'; style-src 'unsafe-inline'; sandbox", resp.Header().Get("Content-Security-Policy"))
|
||||
assert.Equal(t, "image/svg+xml", resp.Header().Get("Content-Type"))
|
||||
assert.Equal(t, "nosniff", resp.Header().Get("X-Content-Type-Options"))
|
||||
})
|
||||
|
||||
t.Run("MimeTypeMap", func(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/user2/repo2/raw/branch/master/test.xml")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
// although the file is a valid XML file, it is served as "text/plain" to avoid site content spamming (the same to "text/html" files)
|
||||
assert.Equal(t, "text/plain; charset=utf-8", resp.Header().Get("Content-Type"))
|
||||
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
defer test.MockVariableValue(&setting.MimeTypeMap)()
|
||||
setting.MimeTypeMap.Enabled = true
|
||||
|
||||
setting.MimeTypeMap.Map[".xml"] = "text/xml"
|
||||
req = NewRequest(t, "GET", "/user2/repo2/raw/branch/master/test.xml")
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
// respect the mime mapping, and "text/plain" protection isn't used anymore
|
||||
assert.Equal(t, "text/xml; charset=utf-8", resp.Header().Get("Content-Type"))
|
||||
assert.Equal(t, "inline; filename=test.xml", resp.Header().Get("Content-Disposition"))
|
||||
|
||||
setting.MimeTypeMap.Map[".xml"] = "application/xml"
|
||||
req = NewRequest(t, "GET", "/user2/repo2/raw/branch/master/test.xml")
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
// non-text file don't have "charset"
|
||||
assert.Equal(t, "application/xml", resp.Header().Get("Content-Type"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestDownloadByIDForSVGUsesSecureHeaders(t *testing.T) {
|
||||
func TestDownloadRepoContentTokenScopes(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
session := loginUser(t, "user2")
|
||||
ownerReadToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository)
|
||||
miscToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadMisc)
|
||||
publicOnlyToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly)
|
||||
|
||||
// Request raw blob
|
||||
req := NewRequest(t, "GET", "/user2/repo2/raw/blob/6395b68e1feebb1e4c657b4f9f6ba2676a283c0b")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
cases := []downloadScopeCase{
|
||||
{
|
||||
name: "PublicArchiveDownload",
|
||||
method: http.MethodGet,
|
||||
url: "/user2/repo1/archive/master.tar.gz",
|
||||
withScope: http.StatusOK,
|
||||
publicOnlyOK: true,
|
||||
},
|
||||
{
|
||||
name: "PrivateArchiveDownload",
|
||||
method: http.MethodGet,
|
||||
url: "/user2/repo2/archive/master.tar.gz",
|
||||
withScope: http.StatusOK,
|
||||
publicOnlyOK: false,
|
||||
},
|
||||
{
|
||||
name: "PublicArchiveInitiate",
|
||||
method: http.MethodPost,
|
||||
url: "/user2/repo1/archive/master.tar.gz",
|
||||
withScope: http.StatusOK,
|
||||
publicOnlyOK: true,
|
||||
},
|
||||
{
|
||||
name: "PrivateArchiveInitiate",
|
||||
method: http.MethodPost,
|
||||
url: "/user2/repo2/archive/master.tar.gz",
|
||||
withScope: http.StatusOK,
|
||||
publicOnlyOK: false,
|
||||
},
|
||||
{
|
||||
name: "PublicRawBlob",
|
||||
method: http.MethodGet,
|
||||
url: "/user2/repo1/raw/blob/4b4851ad51df6a7d9f25c979345979eaeb5b349f",
|
||||
withScope: http.StatusOK,
|
||||
publicOnlyOK: true,
|
||||
},
|
||||
{
|
||||
name: "PublicRawBranch",
|
||||
method: http.MethodGet,
|
||||
url: "/user2/repo1/raw/branch/master/README.md",
|
||||
withScope: http.StatusOK,
|
||||
publicOnlyOK: true,
|
||||
},
|
||||
{
|
||||
name: "PublicRawTag",
|
||||
method: http.MethodGet,
|
||||
url: "/user2/repo1/raw/tag/v1.1/README.md",
|
||||
withScope: http.StatusOK,
|
||||
publicOnlyOK: true,
|
||||
},
|
||||
{
|
||||
name: "PublicRawCommit",
|
||||
method: http.MethodGet,
|
||||
url: "/user2/repo1/raw/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d/README.md",
|
||||
withScope: http.StatusOK,
|
||||
publicOnlyOK: true,
|
||||
},
|
||||
{
|
||||
name: "PublicMediaBlob",
|
||||
method: http.MethodGet,
|
||||
url: "/user2/repo1/media/blob/4b4851ad51df6a7d9f25c979345979eaeb5b349f",
|
||||
withScope: http.StatusOK,
|
||||
publicOnlyOK: true,
|
||||
},
|
||||
{
|
||||
name: "PublicMediaBranch",
|
||||
method: http.MethodGet,
|
||||
url: "/user2/repo1/media/branch/master/README.md",
|
||||
withScope: http.StatusOK,
|
||||
publicOnlyOK: true,
|
||||
},
|
||||
{
|
||||
name: "PublicMediaTag",
|
||||
method: http.MethodGet,
|
||||
url: "/user2/repo1/media/tag/v1.1/README.md",
|
||||
withScope: http.StatusOK,
|
||||
publicOnlyOK: true,
|
||||
},
|
||||
{
|
||||
name: "PublicMediaCommit",
|
||||
method: http.MethodGet,
|
||||
url: "/user2/repo1/media/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d/README.md",
|
||||
withScope: http.StatusOK,
|
||||
publicOnlyOK: true,
|
||||
},
|
||||
{
|
||||
name: "PrivateRawBranch",
|
||||
method: http.MethodGet,
|
||||
url: "/user2/repo2/raw/branch/master/test.xml",
|
||||
withScope: http.StatusOK,
|
||||
publicOnlyOK: false,
|
||||
},
|
||||
{
|
||||
name: "PrivateRawBlob",
|
||||
method: http.MethodGet,
|
||||
url: "/user2/repo2/raw/blob/6395b68e1feebb1e4c657b4f9f6ba2676a283c0b",
|
||||
withScope: http.StatusOK,
|
||||
publicOnlyOK: false,
|
||||
},
|
||||
{
|
||||
name: "PrivateMediaBranch",
|
||||
method: http.MethodGet,
|
||||
url: "/user2/repo2/media/branch/master/test.xml",
|
||||
withScope: http.StatusOK,
|
||||
publicOnlyOK: false,
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, "default-src 'none'; style-src 'unsafe-inline'; sandbox", resp.Header().Get("Content-Security-Policy"))
|
||||
assert.Equal(t, "image/svg+xml", resp.Header().Get("Content-Type"))
|
||||
assert.Equal(t, "nosniff", resp.Header().Get("X-Content-Type-Options"))
|
||||
}
|
||||
|
||||
func TestDownloadByIDMedia(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
// Request raw blob
|
||||
req := NewRequest(t, "GET", "/user2/repo1/media/blob/4b4851ad51df6a7d9f25c979345979eaeb5b349f")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
assert.Equal(t, "# repo1\n\nDescription for repo1", resp.Body.String())
|
||||
}
|
||||
|
||||
func TestDownloadByIDMediaForSVGUsesSecureHeaders(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
// Request raw blob
|
||||
req := NewRequest(t, "GET", "/user2/repo2/media/blob/6395b68e1feebb1e4c657b4f9f6ba2676a283c0b")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
assert.Equal(t, "default-src 'none'; style-src 'unsafe-inline'; sandbox", resp.Header().Get("Content-Security-Policy"))
|
||||
assert.Equal(t, "image/svg+xml", resp.Header().Get("Content-Type"))
|
||||
assert.Equal(t, "nosniff", resp.Header().Get("X-Content-Type-Options"))
|
||||
}
|
||||
|
||||
func TestDownloadRawTextFileWithoutMimeTypeMapping(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
req := NewRequest(t, "GET", "/user2/repo2/raw/branch/master/test.xml")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
assert.Equal(t, "text/plain; charset=utf-8", resp.Header().Get("Content-Type"))
|
||||
}
|
||||
|
||||
func TestDownloadRawTextFileWithMimeTypeMapping(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
setting.MimeTypeMap.Map[".xml"] = "text/xml"
|
||||
setting.MimeTypeMap.Enabled = true
|
||||
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
req := NewRequest(t, "GET", "/user2/repo2/raw/branch/master/test.xml")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
assert.Equal(t, "text/xml; charset=utf-8", resp.Header().Get("Content-Type"))
|
||||
|
||||
delete(setting.MimeTypeMap.Map, ".xml")
|
||||
setting.MimeTypeMap.Enabled = false
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
MakeRequest(t, NewRequest(t, tc.method, tc.url).AddTokenAuth(miscToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, tc.method, tc.url).AddTokenAuth(ownerReadToken), tc.withScope)
|
||||
|
||||
publicOnlyStatus := http.StatusForbidden
|
||||
if tc.publicOnlyOK {
|
||||
publicOnlyStatus = tc.withScope
|
||||
}
|
||||
MakeRequest(t, NewRequest(t, tc.method, tc.url).AddTokenAuth(publicOnlyToken), publicOnlyStatus)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
@@ -86,7 +87,6 @@ func testEditorProtectedBranch(t *testing.T) {
|
||||
session := loginUser(t, "user2")
|
||||
// Change the "master" branch to "protected"
|
||||
req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/edit", map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
"rule_name": "master",
|
||||
"enable_push": "true",
|
||||
})
|
||||
@@ -98,6 +98,42 @@ func testEditorProtectedBranch(t *testing.T) {
|
||||
resp := testEditorActionPostRequest(t, session, "/user2/repo1/_new/master/", map[string]string{"tree_path": "test-protected-branch.txt", "commit_choice": "direct"})
|
||||
assert.Equal(t, http.StatusBadRequest, resp.Code)
|
||||
assert.Equal(t, `Cannot commit to protected branch "master".`, test.ParseJSONError(resp.Body.Bytes()).ErrorMessage)
|
||||
|
||||
// Change "master" branch to mark files under "docs/" as unprotected
|
||||
req = NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/edit", map[string]string{
|
||||
"rule_name": "master",
|
||||
"protected_file_patterns": "",
|
||||
"unprotected_file_patterns": "docs/*.md",
|
||||
"enable_push": "true",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
flashMsg = session.GetCookieFlashMessage()
|
||||
assert.Equal(t, `Branch protection for rule "master" has been updated.`, flashMsg.SuccessMsg)
|
||||
|
||||
resp = testEditorActionPostRequest(t, session, "/user2/repo1/_new/master/docs/new.md", map[string]string{"tree_path": "docs/new.md", "commit_choice": "direct"})
|
||||
assert.Equal(t, http.StatusOK, resp.Code)
|
||||
assert.Contains(t, resp.Body.String(), `"redirect":"/user2/repo1/src/branch/master/docs/new.md"`)
|
||||
|
||||
// Form's destination (renamed.md) is decided by the pre-receive hook, not the controller.
|
||||
resp = testEditorActionPostRequest(t, session, "/user2/repo1/_edit/master/docs/new.md", map[string]string{
|
||||
"content": "renamed via editor",
|
||||
"commit_choice": "direct",
|
||||
"tree_path": "docs/renamed.md",
|
||||
})
|
||||
assert.Equal(t, http.StatusOK, resp.Code)
|
||||
assert.Contains(t, resp.Body.String(), `"redirect":"/user2/repo1/src/branch/master/docs/renamed.md"`)
|
||||
|
||||
// Protected source path: controller rejects up-front regardless of unprotected destination.
|
||||
resp = testEditorActionPostRequest(t, session, "/user2/repo1/_edit/master/README.md", map[string]string{
|
||||
"commit_choice": "direct",
|
||||
"tree_path": "docs/from-readme.md",
|
||||
})
|
||||
assert.Equal(t, http.StatusBadRequest, resp.Code)
|
||||
assert.Equal(t, `Cannot commit to protected branch "master".`, test.ParseJSONError(resp.Body.Bytes()).ErrorMessage)
|
||||
|
||||
resp = testEditorActionPostRequest(t, session, "/user2/repo1/_delete/master/README.md", map[string]string{"commit_choice": "direct"})
|
||||
assert.Equal(t, http.StatusBadRequest, resp.Code)
|
||||
assert.Equal(t, `Cannot commit to protected branch "master".`, test.ParseJSONError(resp.Body.Bytes()).ErrorMessage)
|
||||
}
|
||||
|
||||
func testEditorActionPostRequest(t *testing.T, session *TestSession, requestPath string, params map[string]string) *httptest.ResponseRecorder {
|
||||
@@ -105,7 +141,6 @@ func testEditorActionPostRequest(t *testing.T, session *TestSession, requestPath
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
form := map[string]string{
|
||||
"_csrf": htmlDoc.GetCSRF(),
|
||||
"last_commit": htmlDoc.GetInputValueByName("last_commit"),
|
||||
}
|
||||
maps.Copy(form, params)
|
||||
@@ -149,11 +184,10 @@ func testEditFileToNewBranch(t *testing.T, session *TestSession, user, repo, bra
|
||||
func testEditorDiffPreview(t *testing.T) {
|
||||
session := loginUser(t, "user2")
|
||||
req := NewRequestWithValues(t, "POST", "/user2/repo1/_preview/master/README.md", map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
"content": "Hello, World (Edited)\n",
|
||||
"content": "# repo1 (Edited)",
|
||||
})
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
assert.Contains(t, resp.Body.String(), `<span class="added-code">Hello, World (Edited)</span>`)
|
||||
assert.Contains(t, resp.Body.String(), `<span class="added-code"> (Edited)</span>`)
|
||||
}
|
||||
|
||||
func testEditorPatchFile(t *testing.T) {
|
||||
@@ -187,7 +221,7 @@ func testEditorWebGitCommitEmail(t *testing.T) {
|
||||
require.True(t, user.KeepEmailPrivate)
|
||||
|
||||
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
gitRepo, _ := git.OpenRepository(t.Context(), repo1.RepoPath())
|
||||
gitRepo, _ := gitrepo.OpenRepository(t.Context(), repo1)
|
||||
defer gitRepo.Close()
|
||||
getLastCommit := func(t *testing.T) *git.Commit {
|
||||
c, err := gitRepo.GetBranchCommit("master")
|
||||
@@ -199,7 +233,6 @@ func testEditorWebGitCommitEmail(t *testing.T) {
|
||||
|
||||
makeReq := func(t *testing.T, link string, params map[string]string, expectedUserName, expectedEmail string) *httptest.ResponseRecorder {
|
||||
lastCommit := getLastCommit(t)
|
||||
params["_csrf"] = GetUserCSRFToken(t, session)
|
||||
params["last_commit"] = lastCommit.ID.String()
|
||||
params["commit_choice"] = "direct"
|
||||
req := NewRequestWithValues(t, "POST", link, params)
|
||||
@@ -224,7 +257,6 @@ func testEditorWebGitCommitEmail(t *testing.T) {
|
||||
uploadForm := multipart.NewWriter(body)
|
||||
file, _ := uploadForm.CreateFormFile("file", name)
|
||||
_, _ = io.Copy(file, strings.NewReader(content))
|
||||
_ = uploadForm.WriteField("_csrf", GetUserCSRFToken(t, session))
|
||||
_ = uploadForm.Close()
|
||||
|
||||
req := NewRequestWithBody(t, "POST", "/user2/repo1/upload-file", body)
|
||||
@@ -262,7 +294,7 @@ func testEditorWebGitCommitEmail(t *testing.T) {
|
||||
t.Run("DefaultEmailKeepPrivate", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
paramsForKeepPrivate["commit_email"] = ""
|
||||
resp1 = makeReq(t, linkForKeepPrivate, paramsForKeepPrivate, "User Two", "user2@noreply.example.org")
|
||||
resp1 = makeReq(t, linkForKeepPrivate, paramsForKeepPrivate, "User Two", "2+user2@noreply.example.org")
|
||||
})
|
||||
t.Run("ChooseEmail", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
@@ -346,7 +378,7 @@ func testForkToEditFile(t *testing.T, session *TestSession, user, owner, repo, b
|
||||
assert.Contains(t, resp.Body.String(), "Fork Repository to Propose Changes")
|
||||
|
||||
// fork the repository
|
||||
req = NewRequestWithValues(t, "POST", path.Join(owner, repo, "_fork", branch), map[string]string{"_csrf": GetUserCSRFToken(t, session)})
|
||||
req = NewRequest(t, "POST", path.Join(owner, repo, "_fork", branch))
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
assert.JSONEq(t, `{"redirect":""}`, resp.Body.String())
|
||||
}
|
||||
@@ -358,7 +390,6 @@ func testForkToEditFile(t *testing.T, session *TestSession, user, owner, repo, b
|
||||
// Archive the repository
|
||||
req := NewRequestWithValues(t, "POST", path.Join(user, repo, "settings"),
|
||||
map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
"repo_name": repo,
|
||||
"action": "archive",
|
||||
},
|
||||
@@ -373,7 +404,6 @@ func testForkToEditFile(t *testing.T, session *TestSession, user, owner, repo, b
|
||||
// Unfork the repository
|
||||
req = NewRequestWithValues(t, "POST", path.Join(user, repo, "settings"),
|
||||
map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
"repo_name": repo,
|
||||
"action": "convert_fork",
|
||||
},
|
||||
@@ -409,7 +439,6 @@ func testForkToEditFile(t *testing.T, session *TestSession, user, owner, repo, b
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
editRequestForm := map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
"last_commit": htmlDoc.GetInputValueByName("last_commit"),
|
||||
"tree_path": filePath,
|
||||
"content": "new content in fork",
|
||||
|
||||
@@ -32,7 +32,6 @@ import (
|
||||
func testAPINewFile(t *testing.T, session *TestSession, user, repo, branch, treePath, content string) {
|
||||
url := fmt.Sprintf("/%s/%s/_new/%s", user, repo, branch)
|
||||
req := NewRequestWithValues(t, "POST", url, map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
"commit_choice": "direct",
|
||||
"tree_path": treePath,
|
||||
"content": content,
|
||||
@@ -86,7 +85,6 @@ func TestEmptyRepoAddFile(t *testing.T) {
|
||||
doc := NewHTMLParser(t, resp.Body).Find(`input[name="commit_choice"]`)
|
||||
assert.Empty(t, doc.AttrOr("checked", "_no_"))
|
||||
req = NewRequestWithValues(t, "POST", "/user30/empty/_new/"+setting.Repository.DefaultBranch, map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
"commit_choice": "direct",
|
||||
"tree_path": "test-file.md",
|
||||
"content": "newly-added-test-file",
|
||||
@@ -142,7 +140,6 @@ func TestEmptyRepoUploadFile(t *testing.T) {
|
||||
|
||||
body := &bytes.Buffer{}
|
||||
mpForm := multipart.NewWriter(body)
|
||||
_ = mpForm.WriteField("_csrf", GetUserCSRFToken(t, session))
|
||||
file, _ := mpForm.CreateFormFile("file", "uploaded-file.txt")
|
||||
_, _ = io.Copy(file, strings.NewReader("newly-uploaded-test-file"))
|
||||
_ = mpForm.Close()
|
||||
@@ -154,7 +151,6 @@ func TestEmptyRepoUploadFile(t *testing.T) {
|
||||
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), &respMap))
|
||||
|
||||
req = NewRequestWithValues(t, "POST", "/user30/empty/_upload/"+setting.Repository.DefaultBranch, map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
"commit_choice": "direct",
|
||||
"files": respMap["uuid"],
|
||||
"tree_path": "",
|
||||
|
||||
@@ -194,14 +194,15 @@ func lfsCommitAndPushTest(t *testing.T, dstPath string, sizes ...int) (pushedFil
|
||||
t.Run("CommitAndPushLFS", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
prefix := "lfs-data-file-"
|
||||
err := gitcmd.NewCommand("lfs").AddArguments("install").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
|
||||
err := gitcmd.NewCommand("lfs").AddArguments("install").WithDir(dstPath).Run(t.Context())
|
||||
assert.NoError(t, err)
|
||||
_, _, err = gitcmd.NewCommand("lfs").AddArguments("track").AddDynamicArguments(prefix+"*").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
|
||||
_, _, err = gitcmd.NewCommand("lfs").AddArguments("track").AddDynamicArguments(prefix + "*").
|
||||
WithDir(dstPath).RunStdString(t.Context())
|
||||
assert.NoError(t, err)
|
||||
err = git.AddChanges(t.Context(), dstPath, false, ".gitattributes")
|
||||
err = gitAddChangesDeprecated(t.Context(), dstPath, false, ".gitattributes")
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = git.CommitChanges(t.Context(), dstPath, git.CommitChangesOptions{
|
||||
err = gitCommitChangesDeprecated(t.Context(), dstPath, gitCommitChangesOptions{
|
||||
Committer: &git.Signature{
|
||||
Email: "user2@example.com",
|
||||
Name: "User Two",
|
||||
@@ -312,20 +313,20 @@ func lockTest(t *testing.T, repoPath string) {
|
||||
}
|
||||
|
||||
func lockFileTest(t *testing.T, filename, repoPath string) {
|
||||
_, _, err := gitcmd.NewCommand("lfs").AddArguments("locks").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repoPath})
|
||||
_, _, err := gitcmd.NewCommand("lfs").AddArguments("locks").WithDir(repoPath).RunStdString(t.Context())
|
||||
assert.NoError(t, err)
|
||||
_, _, err = gitcmd.NewCommand("lfs").AddArguments("lock").AddDynamicArguments(filename).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repoPath})
|
||||
_, _, err = gitcmd.NewCommand("lfs").AddArguments("lock").AddDynamicArguments(filename).WithDir(repoPath).RunStdString(t.Context())
|
||||
assert.NoError(t, err)
|
||||
_, _, err = gitcmd.NewCommand("lfs").AddArguments("locks").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repoPath})
|
||||
_, _, err = gitcmd.NewCommand("lfs").AddArguments("locks").WithDir(repoPath).RunStdString(t.Context())
|
||||
assert.NoError(t, err)
|
||||
_, _, err = gitcmd.NewCommand("lfs").AddArguments("unlock").AddDynamicArguments(filename).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repoPath})
|
||||
_, _, err = gitcmd.NewCommand("lfs").AddArguments("unlock").AddDynamicArguments(filename).WithDir(repoPath).RunStdString(t.Context())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func doCommitAndPush(t *testing.T, size int, repoPath, prefix string) string {
|
||||
name, err := generateCommitWithNewData(t.Context(), size, repoPath, "user2@example.com", "User Two", prefix)
|
||||
assert.NoError(t, err)
|
||||
_, _, err = gitcmd.NewCommand("push", "origin", "master").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repoPath}) // Push
|
||||
_, _, err = gitcmd.NewCommand("push", "origin", "master").WithDir(repoPath).RunStdString(t.Context())
|
||||
assert.NoError(t, err)
|
||||
return name
|
||||
}
|
||||
@@ -346,11 +347,11 @@ func generateCommitWithNewData(ctx context.Context, size int, repoPath, email, f
|
||||
_ = tmpFile.Close()
|
||||
|
||||
// Commit
|
||||
err = git.AddChanges(ctx, repoPath, false, filepath.Base(tmpFile.Name()))
|
||||
err = gitAddChangesDeprecated(ctx, repoPath, false, filepath.Base(tmpFile.Name()))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = git.CommitChanges(ctx, repoPath, git.CommitChangesOptions{
|
||||
err = gitCommitChangesDeprecated(ctx, repoPath, gitCommitChangesOptions{
|
||||
Committer: &git.Signature{
|
||||
Email: email,
|
||||
Name: fullName,
|
||||
@@ -425,7 +426,7 @@ func doBranchProtectPRMerge(baseCtx *APITestContext, dstPath string) func(t *tes
|
||||
// Try to force push without force push permissions, which should fail
|
||||
t.Run("ForcePushWithoutForcePermissions", func(t *testing.T) {
|
||||
t.Run("CreateDivergentHistory", func(t *testing.T) {
|
||||
gitcmd.NewCommand("reset", "--hard", "HEAD~1").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
|
||||
gitcmd.NewCommand("reset", "--hard", "HEAD~1").WithDir(dstPath).Run(t.Context())
|
||||
_, err := generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-new")
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
@@ -506,10 +507,7 @@ type doProtectBranchOptions struct {
|
||||
func doProtectBranchExt(ctx APITestContext, ruleName string, opts doProtectBranchOptions) func(t *testing.T) {
|
||||
// We are going to just use the owner to set the protection.
|
||||
return func(t *testing.T) {
|
||||
csrf := GetUserCSRFToken(t, ctx.Session)
|
||||
|
||||
formData := map[string]string{
|
||||
"_csrf": csrf,
|
||||
"rule_name": ruleName,
|
||||
"unprotected_file_patterns": opts.UnprotectedFilePatterns,
|
||||
"protected_file_patterns": opts.ProtectedFilePatterns,
|
||||
@@ -693,11 +691,7 @@ func doPushCreate(ctx APITestContext, u *url.URL) func(t *testing.T) {
|
||||
|
||||
func doBranchDelete(ctx APITestContext, owner, repo, branch string) func(*testing.T) {
|
||||
return func(t *testing.T) {
|
||||
csrf := GetUserCSRFToken(t, ctx.Session)
|
||||
|
||||
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/branches/delete?name=%s", url.PathEscape(owner), url.PathEscape(repo), url.QueryEscape(branch)), map[string]string{
|
||||
"_csrf": csrf,
|
||||
})
|
||||
req := NewRequest(t, "POST", fmt.Sprintf("/%s/%s/branches/delete?name=%s", url.PathEscape(owner), url.PathEscape(repo), url.QueryEscape(branch)))
|
||||
ctx.Session.MakeRequest(t, req, http.StatusOK)
|
||||
}
|
||||
}
|
||||
@@ -843,10 +837,10 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string
|
||||
err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content"), 0o666)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = git.AddChanges(t.Context(), dstPath, true)
|
||||
err = gitAddChangesDeprecated(t.Context(), dstPath, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = git.CommitChanges(t.Context(), dstPath, git.CommitChangesOptions{
|
||||
err = gitCommitChangesDeprecated(t.Context(), dstPath, gitCommitChangesOptions{
|
||||
Committer: &git.Signature{
|
||||
Email: "user2@example.com",
|
||||
Name: "user2",
|
||||
@@ -865,7 +859,10 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string
|
||||
})
|
||||
|
||||
t.Run("Push", func(t *testing.T) {
|
||||
err := gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic="+headBranch).Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
|
||||
err := gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master", "-o").
|
||||
AddDynamicArguments("topic=" + headBranch).
|
||||
WithDir(dstPath).
|
||||
Run(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+1)
|
||||
@@ -883,7 +880,10 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string
|
||||
assert.Contains(t, "Testing commit 1", prMsg.Body)
|
||||
assert.Equal(t, commit, prMsg.Head.Sha)
|
||||
|
||||
_, _, err = gitcmd.NewCommand("push", "origin").AddDynamicArguments("HEAD:refs/for/master/test/"+headBranch).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
|
||||
_, _, err = gitcmd.NewCommand("push", "origin").
|
||||
AddDynamicArguments("HEAD:refs/for/master/test/" + headBranch).
|
||||
WithDir(dstPath).
|
||||
RunStdString(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2)
|
||||
@@ -909,10 +909,10 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string
|
||||
err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content \n ## test content 2"), 0o666)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = git.AddChanges(t.Context(), dstPath, true)
|
||||
err = gitAddChangesDeprecated(t.Context(), dstPath, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = git.CommitChanges(t.Context(), dstPath, git.CommitChangesOptions{
|
||||
err = gitCommitChangesDeprecated(t.Context(), dstPath, gitCommitChangesOptions{
|
||||
Committer: &git.Signature{
|
||||
Email: "user2@example.com",
|
||||
Name: "user2",
|
||||
@@ -931,7 +931,10 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string
|
||||
})
|
||||
|
||||
t.Run("Push2", func(t *testing.T) {
|
||||
err := gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic="+headBranch).Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
|
||||
err := gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master", "-o").
|
||||
AddDynamicArguments("topic=" + headBranch).
|
||||
WithDir(dstPath).
|
||||
Run(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2)
|
||||
@@ -941,7 +944,10 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string
|
||||
assert.False(t, prMsg.HasMerged)
|
||||
assert.Equal(t, commit, prMsg.Head.Sha)
|
||||
|
||||
_, _, err = gitcmd.NewCommand("push", "origin").AddDynamicArguments("HEAD:refs/for/master/test/"+headBranch).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
|
||||
_, _, err = gitcmd.NewCommand("push", "origin").
|
||||
AddDynamicArguments("HEAD:refs/for/master/test/" + headBranch).
|
||||
WithDir(dstPath).
|
||||
RunStdString(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2)
|
||||
|
||||
@@ -58,6 +58,52 @@ func createSSHUrl(gitPath string, u *url.URL) *url.URL {
|
||||
return &u2
|
||||
}
|
||||
|
||||
// gitAddChangesDeprecated marks local changes to be ready for commit.
|
||||
// Deprecated: use "git fast-import" instead for better performance and more control over the commit creation.
|
||||
func gitAddChangesDeprecated(ctx context.Context, repoPath string, all bool, files ...string) error {
|
||||
cmd := gitcmd.NewCommand().AddArguments("add")
|
||||
if all {
|
||||
cmd.AddArguments("--all")
|
||||
}
|
||||
cmd.AddDashesAndList(files...)
|
||||
_, _, err := cmd.WithDir(repoPath).RunStdString(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// CommitChangesOptions the options when a commit created
|
||||
type gitCommitChangesOptions struct {
|
||||
Committer *git.Signature
|
||||
Author *git.Signature
|
||||
Message string
|
||||
}
|
||||
|
||||
// gitCommitChangesDeprecated commits local changes with given committer, author and message.
|
||||
// If author is nil, it will be the same as committer.
|
||||
// Deprecated: use "git fast-import" instead for better performance and more control over the commit creation.
|
||||
func gitCommitChangesDeprecated(ctx context.Context, repoPath string, opts gitCommitChangesOptions) error {
|
||||
cmd := gitcmd.NewCommand()
|
||||
if opts.Committer != nil {
|
||||
cmd.AddOptionValues("-c", "user.name="+opts.Committer.Name)
|
||||
cmd.AddOptionValues("-c", "user.email="+opts.Committer.Email)
|
||||
}
|
||||
cmd.AddArguments("commit")
|
||||
|
||||
if opts.Author == nil {
|
||||
opts.Author = opts.Committer
|
||||
}
|
||||
if opts.Author != nil {
|
||||
cmd.AddOptionFormat("--author='%s <%s>'", opts.Author.Name, opts.Author.Email)
|
||||
}
|
||||
cmd.AddOptionFormat("--message=%s", opts.Message)
|
||||
|
||||
_, _, err := cmd.WithDir(repoPath).RunStdString(ctx)
|
||||
// No stderr but exit status 1 means nothing to commit.
|
||||
if gitcmd.IsErrorExitCode(err, 1) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func onGiteaRun[T testing.TB](t T, callback func(T, *url.URL)) {
|
||||
defer tests.PrepareTestEnv(t, 1)()
|
||||
s := http.Server{
|
||||
@@ -123,16 +169,18 @@ func doGitInitTestRepository(dstPath string) func(*testing.T) {
|
||||
// Init repository in dstPath
|
||||
assert.NoError(t, git.InitRepository(t.Context(), dstPath, false, git.Sha1ObjectFormat.Name()))
|
||||
// forcibly set default branch to master
|
||||
_, _, err := gitcmd.NewCommand("symbolic-ref", "HEAD", git.BranchPrefix+"master").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
|
||||
_, _, err := gitcmd.NewCommand("symbolic-ref", "HEAD", git.BranchPrefix+"master").
|
||||
WithDir(dstPath).
|
||||
RunStdString(t.Context())
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(dstPath, "README.md"), []byte("# Testing Repository\n\nOriginally created in: "+dstPath), 0o644))
|
||||
assert.NoError(t, git.AddChanges(t.Context(), dstPath, true))
|
||||
assert.NoError(t, gitAddChangesDeprecated(t.Context(), dstPath, true))
|
||||
signature := git.Signature{
|
||||
Email: "test@example.com",
|
||||
Name: "test",
|
||||
When: time.Now(),
|
||||
}
|
||||
assert.NoError(t, git.CommitChanges(t.Context(), dstPath, git.CommitChangesOptions{
|
||||
assert.NoError(t, gitCommitChangesDeprecated(t.Context(), dstPath, gitCommitChangesOptions{
|
||||
Committer: &signature,
|
||||
Author: &signature,
|
||||
Message: "Initial Commit",
|
||||
@@ -142,21 +190,27 @@ func doGitInitTestRepository(dstPath string) func(*testing.T) {
|
||||
|
||||
func doGitAddRemote(dstPath, remoteName string, u *url.URL) func(*testing.T) {
|
||||
return func(t *testing.T) {
|
||||
_, _, err := gitcmd.NewCommand("remote", "add").AddDynamicArguments(remoteName, u.String()).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
|
||||
_, _, err := gitcmd.NewCommand("remote", "add").AddDynamicArguments(remoteName, u.String()).
|
||||
WithDir(dstPath).
|
||||
RunStdString(t.Context())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func doGitPushTestRepository(dstPath string, args ...string) func(*testing.T) {
|
||||
return func(t *testing.T) {
|
||||
_, _, err := gitcmd.NewCommand("push", "-u").AddArguments(gitcmd.ToTrustedCmdArgs(args)...).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
|
||||
_, _, err := gitcmd.NewCommand("push", "-u").AddArguments(gitcmd.ToTrustedCmdArgs(args)...).
|
||||
WithDir(dstPath).
|
||||
RunStdString(t.Context())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func doGitPushTestRepositoryFail(dstPath string, args ...string) func(*testing.T) {
|
||||
return func(t *testing.T) {
|
||||
_, _, err := gitcmd.NewCommand("push").AddArguments(gitcmd.ToTrustedCmdArgs(args)...).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
|
||||
_, _, err := gitcmd.NewCommand("push").AddArguments(gitcmd.ToTrustedCmdArgs(args)...).
|
||||
WithDir(dstPath).
|
||||
RunStdString(t.Context())
|
||||
assert.Error(t, err)
|
||||
}
|
||||
}
|
||||
@@ -173,12 +227,12 @@ func doGitCheckoutWriteFileCommit(opts localGitAddCommitOptions) func(*testing.T
|
||||
doGitCheckoutBranch(opts.LocalRepoPath, opts.CheckoutBranch)(t)
|
||||
localFilePath := filepath.Join(opts.LocalRepoPath, opts.TreeFilePath)
|
||||
require.NoError(t, os.WriteFile(localFilePath, []byte(opts.TreeFileContent), 0o644))
|
||||
require.NoError(t, git.AddChanges(t.Context(), opts.LocalRepoPath, true))
|
||||
require.NoError(t, gitAddChangesDeprecated(t.Context(), opts.LocalRepoPath, true))
|
||||
signature := git.Signature{
|
||||
Email: "test@test.test",
|
||||
Name: "test",
|
||||
}
|
||||
require.NoError(t, git.CommitChanges(t.Context(), opts.LocalRepoPath, git.CommitChangesOptions{
|
||||
require.NoError(t, gitCommitChangesDeprecated(t.Context(), opts.LocalRepoPath, gitCommitChangesOptions{
|
||||
Committer: &signature,
|
||||
Author: &signature,
|
||||
Message: fmt.Sprintf("update %s @ %s", opts.TreeFilePath, opts.CheckoutBranch),
|
||||
@@ -188,28 +242,48 @@ func doGitCheckoutWriteFileCommit(opts localGitAddCommitOptions) func(*testing.T
|
||||
|
||||
func doGitCreateBranch(dstPath, branch string) func(*testing.T) {
|
||||
return func(t *testing.T) {
|
||||
_, _, err := gitcmd.NewCommand("checkout", "-b").AddDynamicArguments(branch).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
|
||||
_, _, err := gitcmd.NewCommand("checkout", "-b").AddDynamicArguments(branch).
|
||||
WithDir(dstPath).RunStdString(t.Context())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func doGitCheckoutBranch(dstPath string, args ...string) func(*testing.T) {
|
||||
return func(t *testing.T) {
|
||||
_, _, err := gitcmd.NewCommand().AddArguments("checkout").AddArguments(gitcmd.ToTrustedCmdArgs(args)...).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
|
||||
_, _, err := gitcmd.NewCommand().AddArguments("checkout").
|
||||
AddArguments(gitcmd.ToTrustedCmdArgs(args)...).
|
||||
WithDir(dstPath).
|
||||
RunStdString(t.Context())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func doGitMerge(dstPath string, args ...string) func(*testing.T) {
|
||||
return func(t *testing.T) {
|
||||
_, _, err := gitcmd.NewCommand("merge").AddArguments(gitcmd.ToTrustedCmdArgs(args)...).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
|
||||
_, _, err := gitcmd.NewCommand("merge").AddArguments(gitcmd.ToTrustedCmdArgs(args)...).
|
||||
WithDir(dstPath).
|
||||
RunStdString(t.Context())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func doGitPull(dstPath string, args ...string) func(*testing.T) {
|
||||
return func(t *testing.T) {
|
||||
_, _, err := gitcmd.NewCommand().AddArguments("pull").AddArguments(gitcmd.ToTrustedCmdArgs(args)...).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
|
||||
_, _, err := gitcmd.NewCommand().AddArguments("pull").AddArguments(gitcmd.ToTrustedCmdArgs(args)...).
|
||||
WithDir(dstPath).
|
||||
RunStdString(t.Context())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
// doGitRemoteArchive runs a git archive command requesting an archive from remote
|
||||
// and verifies that the command did not error out and returned only normal output
|
||||
func doGitRemoteArchive(remote string, args ...string) func(*testing.T) {
|
||||
return func(t *testing.T) {
|
||||
stdout, stderr, err := gitcmd.NewCommand("archive").AddOptionValues("--remote", remote).AddArguments(gitcmd.ToTrustedCmdArgs(args)...).
|
||||
RunStdString(t.Context())
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, stderr)
|
||||
assert.NotEmpty(t, stdout)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,9 @@ func TestGitLFSSSH(t *testing.T) {
|
||||
setting.LFS.AllowPureSSH = true
|
||||
require.NoError(t, cfg.Save())
|
||||
|
||||
_, _, cmdErr := gitcmd.NewCommand("config", "lfs.sshtransfer", "always").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: localRepoForUpload})
|
||||
_, _, cmdErr := gitcmd.NewCommand("config", "lfs.sshtransfer", "always").
|
||||
WithDir(localRepoForUpload).
|
||||
RunStdString(t.Context())
|
||||
assert.NoError(t, cmdErr)
|
||||
pushedFiles := lfsCommitAndPushTest(t, localRepoForUpload, 10)
|
||||
|
||||
|
||||
@@ -104,7 +104,8 @@ func TestAgitPullPush(t *testing.T) {
|
||||
err = gitcmd.NewCommand("push", "origin",
|
||||
"-o", "title=test-title", "-o", "description=test-description",
|
||||
"HEAD:refs/for/master/test-agit-push",
|
||||
).Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
|
||||
).WithDir(dstPath).
|
||||
Run(t.Context())
|
||||
assert.NoError(t, err)
|
||||
|
||||
// check pull request exist
|
||||
@@ -118,20 +119,26 @@ func TestAgitPullPush(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
// push 2
|
||||
err = gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
|
||||
err = gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push").
|
||||
WithDir(dstPath).
|
||||
Run(t.Context())
|
||||
assert.NoError(t, err)
|
||||
|
||||
// reset to first commit
|
||||
err = gitcmd.NewCommand("reset", "--hard", "HEAD~1").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
|
||||
err = gitcmd.NewCommand("reset", "--hard", "HEAD~1").WithDir(dstPath).Run(t.Context())
|
||||
assert.NoError(t, err)
|
||||
|
||||
// test force push without confirm
|
||||
_, stderr, err := gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
|
||||
_, stderr, err := gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push").
|
||||
WithDir(dstPath).
|
||||
RunStdString(t.Context())
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, stderr, "[remote rejected] HEAD -> refs/for/master/test-agit-push (request `force-push` push option)")
|
||||
|
||||
// test force push with confirm
|
||||
err = gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push", "-o", "force-push").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
|
||||
err = gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push", "-o", "force-push").
|
||||
WithDir(dstPath).
|
||||
Run(t.Context())
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
@@ -160,7 +167,7 @@ func TestAgitReviewStaleness(t *testing.T) {
|
||||
err = gitcmd.NewCommand("push", "origin",
|
||||
"-o", "title=Test agit Review Staleness", "-o", "description=Testing review staleness",
|
||||
"HEAD:refs/for/master/test-agit-review",
|
||||
).Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
|
||||
).WithDir(dstPath).Run(t.Context())
|
||||
assert.NoError(t, err)
|
||||
|
||||
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
|
||||
@@ -200,7 +207,9 @@ func TestAgitReviewStaleness(t *testing.T) {
|
||||
_, err = generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "updated-")
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-review").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
|
||||
err = gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-review").
|
||||
WithDir(dstPath).
|
||||
Run(t.Context())
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Reload PR to get updated commit ID
|
||||
|
||||
@@ -210,9 +210,7 @@ func TestPushPullRefs(t *testing.T) {
|
||||
doGitClone(dstPath, u)(t)
|
||||
|
||||
cmd := gitcmd.NewCommand("push", "--delete", "origin", "refs/pull/2/head")
|
||||
stdout, stderr, err := cmd.RunStdString(t.Context(), &gitcmd.RunOpts{
|
||||
Dir: dstPath,
|
||||
})
|
||||
stdout, stderr, err := cmd.WithDir(dstPath).RunStdString(t.Context())
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, stdout)
|
||||
assert.NotContains(t, stderr, "[deleted]", "stderr: %s", stderr)
|
||||
|
||||
@@ -9,6 +9,9 @@ import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
@@ -20,7 +23,9 @@ import (
|
||||
func TestGitSmartHTTP(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
testGitSmartHTTP(t, u)
|
||||
testGitSmartHTTPTokenScopes(t)
|
||||
testRenamedRepoRedirect(t)
|
||||
testGitArchiveRemote(t, u)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -79,6 +84,42 @@ func testGitSmartHTTP(t *testing.T, u *url.URL) {
|
||||
}
|
||||
}
|
||||
|
||||
func testGitSmartHTTPTokenScopes(t *testing.T) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2, OwnerName: "user2", Name: "repo2"})
|
||||
require.True(t, repo.IsPrivate)
|
||||
|
||||
session := loginUser(t, "user2")
|
||||
badToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadNotification)
|
||||
readToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
|
||||
writeToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopePublicOnly, auth_model.AccessTokenScopeReadRepository)
|
||||
|
||||
t.Run("upload-pack requires read repository scope", func(t *testing.T) {
|
||||
path := "/user2/repo2/info/refs?service=git-upload-pack"
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", path).AddBasicAuth(badToken, "x-oauth-basic"), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "GET", path).AddTokenAuth(badToken), http.StatusForbidden)
|
||||
|
||||
resp := MakeRequest(t, NewRequest(t, "GET", path).AddTokenAuth(readToken), http.StatusOK)
|
||||
assert.Contains(t, resp.Body.String(), "refs/heads/master")
|
||||
})
|
||||
|
||||
t.Run("receive-pack requires write repository scope", func(t *testing.T) {
|
||||
path := "/user2/repo2/info/refs?service=git-receive-pack"
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", path).AddBasicAuth(readToken, "x-oauth-basic"), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "GET", path).AddTokenAuth(readToken), http.StatusForbidden)
|
||||
|
||||
resp := MakeRequest(t, NewRequest(t, "GET", path).AddTokenAuth(writeToken), http.StatusOK)
|
||||
assert.Contains(t, resp.Body.String(), "refs/heads/master")
|
||||
})
|
||||
|
||||
t.Run("public-only scope rejects private repo", func(t *testing.T) {
|
||||
path := "/user2/repo2/info/refs?service=git-upload-pack"
|
||||
MakeRequest(t, NewRequest(t, "GET", path).AddTokenAuth(publicOnlyToken), http.StatusForbidden)
|
||||
})
|
||||
}
|
||||
|
||||
func testRenamedRepoRedirect(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)()
|
||||
|
||||
@@ -96,3 +137,10 @@ func testRenamedRepoRedirect(t *testing.T) {
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
assert.Contains(t, resp.Body.String(), "65f1bf27bc3bf70f64657658635e66094edbcb4d\trefs/tags/v1.1")
|
||||
}
|
||||
|
||||
func testGitArchiveRemote(t *testing.T, u *url.URL) {
|
||||
u = u.JoinPath("user27/repo49.git")
|
||||
t.Run("Fetch HEAD archive", doGitRemoteArchive(u.String(), "HEAD"))
|
||||
t.Run("Fetch HEAD archive subpath", doGitRemoteArchive(u.String(), "HEAD", "test"))
|
||||
t.Run("list compression options", doGitRemoteArchive(u.String(), "--list"))
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
@@ -255,6 +256,52 @@ func testGitSigning(t *testing.T) {
|
||||
assert.True(t, branch.Commit.Verification.Verified)
|
||||
}))
|
||||
})
|
||||
|
||||
setting.Repository.Signing.Merges = []string{"commitssigned"}
|
||||
setting.Repository.Signing.CRUDActions = []string{"always"}
|
||||
t.Run("UpdateRebaseSigned", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
testCtx := NewAPITestContext(t, username, "update-rebase-signed", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreateRepository", doAPICreateRepository(testCtx, false))
|
||||
|
||||
var repoID int64
|
||||
t.Run("GetRepository", doAPIGetRepository(testCtx, func(t *testing.T, repo api.Repository) {
|
||||
repoID = repo.ID
|
||||
}))
|
||||
enableRepoAllowUpdateWithRebase(t, repoID, true)
|
||||
|
||||
t.Run("CreateFeatureCommit", crudActionCreateFile(
|
||||
t, testCtx, user, "master", "feature", "signed-feature.txt"))
|
||||
pr, err := doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "feature")(t)
|
||||
require.NoError(t, err)
|
||||
|
||||
content := base64.StdEncoding.EncodeToString([]byte("update base"))
|
||||
t.Run("UpdateBase", doAPICreateFile(testCtx, "signed-base.txt", &api.CreateFileOptions{
|
||||
FileOptions: api.FileOptions{
|
||||
BranchName: "master",
|
||||
Message: "update base",
|
||||
Author: api.Identity{
|
||||
Name: user.FullName,
|
||||
Email: user.Email,
|
||||
},
|
||||
Committer: api.Identity{
|
||||
Name: user.FullName,
|
||||
Email: user.Email,
|
||||
},
|
||||
},
|
||||
ContentBase64: content,
|
||||
}))
|
||||
|
||||
req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/pulls/%d/update?style=rebase", testCtx.Username, testCtx.Reponame, pr.Index).
|
||||
AddTokenAuth(testCtx.Token)
|
||||
testCtx.Session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
t.Run("CheckFeatureBranchSigned", doAPIGetBranch(testCtx, "feature", func(t *testing.T, branch api.Branch) {
|
||||
require.NotNil(t, branch.Commit)
|
||||
require.NotNil(t, branch.Commit.Verification)
|
||||
assert.True(t, branch.Commit.Verification.Verified)
|
||||
}))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHeatmapEndpoints(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
// Mock time so fixture actions fall within the heatmap's time window
|
||||
timeutil.MockSet(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
defer timeutil.MockUnset()
|
||||
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
t.Run("UserProfile", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
req := NewRequest(t, "GET", "/user2/-/heatmap")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var result map[string]any
|
||||
DecodeJSON(t, resp, &result)
|
||||
assert.Contains(t, result, "heatmapData")
|
||||
assert.Contains(t, result, "totalContributions")
|
||||
assert.Positive(t, result["totalContributions"])
|
||||
})
|
||||
|
||||
t.Run("OrgDashboard", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
req := NewRequest(t, "GET", "/org/org3/dashboard/-/heatmap")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var result map[string]any
|
||||
DecodeJSON(t, resp, &result)
|
||||
assert.Contains(t, result, "heatmapData")
|
||||
assert.Contains(t, result, "totalContributions")
|
||||
})
|
||||
|
||||
t.Run("OrgTeamDashboard", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
req := NewRequest(t, "GET", "/org/org3/dashboard/-/heatmap/team1")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var result map[string]any
|
||||
DecodeJSON(t, resp, &result)
|
||||
assert.Contains(t, result, "heatmapData")
|
||||
assert.Contains(t, result, "totalContributions")
|
||||
})
|
||||
}
|
||||
@@ -37,11 +37,6 @@ func (doc *HTMLDoc) Find(selector string) *goquery.Selection {
|
||||
return doc.doc.Find(selector)
|
||||
}
|
||||
|
||||
// GetCSRF for getting CSRF token value from input
|
||||
func (doc *HTMLDoc) GetCSRF() string {
|
||||
return doc.GetInputValueByName("_csrf")
|
||||
}
|
||||
|
||||
// AssertHTMLElement check if the element by selector exists or does not exist depending on checkExists
|
||||
func AssertHTMLElement[T int | bool](t testing.TB, doc *HTMLDoc, selector string, checkExists T) {
|
||||
sel := doc.doc.Find(selector)
|
||||
|
||||
@@ -66,7 +66,14 @@ func TestIncomingEmail(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
ht, u, p, err := token_service.ExtractToken(t.Context(), token)
|
||||
ht, u, p, err := token_service.DecodeToken(t.Context(), token)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, token_service.ReplyHandlerType, ht)
|
||||
assert.Equal(t, user.ID, u.ID)
|
||||
assert.Equal(t, payload, p)
|
||||
|
||||
// MTAs may lowercase the local-part of the reply-to address (RFC 5321 §2.4).
|
||||
ht, u, p, err = token_service.DecodeToken(t.Context(), strings.ToLower(token))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, token_service.ReplyHandlerType, ht)
|
||||
assert.Equal(t, user.ID, u.ID)
|
||||
@@ -189,7 +196,7 @@ func TestIncomingEmail(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
msg := sender_service.NewMessageFrom(
|
||||
strings.Replace(setting.IncomingEmail.ReplyToAddress, setting.IncomingEmail.TokenPlaceholder, token, 1),
|
||||
strings.Replace(setting.IncomingEmail.ReplyToAddress, setting.IncomingEmailTokenPlaceholder, token, 1),
|
||||
"",
|
||||
user.Email,
|
||||
"",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//nolint:forbidigo // use of print functions is allowed in tests
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"hash"
|
||||
"hash/fnv"
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/testlogger"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
@@ -79,14 +80,14 @@ func NewNilResponseHashSumRecorder() *NilResponseHashSumRecorder {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
func testMain(m *testing.M) int {
|
||||
defer log.GetManager().Close()
|
||||
|
||||
managerCtx, cancel := context.WithCancel(context.Background())
|
||||
graceful.InitManager(managerCtx)
|
||||
defer cancel()
|
||||
|
||||
tests.InitTest(true)
|
||||
tests.InitTest()
|
||||
testWebRoutes = routers.NormalRoutes()
|
||||
|
||||
err := unittest.InitFixtures(
|
||||
@@ -95,8 +96,7 @@ func TestMain(m *testing.M) {
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Printf("Error initializing test database: %v\n", err)
|
||||
os.Exit(1)
|
||||
testlogger.Panicf("InitFixtures: %v", err)
|
||||
}
|
||||
|
||||
// FIXME: the console logger is deleted by mistake, so if there is any `log.Fatal`, developers won't see any error message.
|
||||
@@ -104,15 +104,16 @@ func TestMain(m *testing.M) {
|
||||
exitCode := m.Run()
|
||||
|
||||
if err = util.RemoveAll(setting.Indexer.IssuePath); err != nil {
|
||||
fmt.Printf("util.RemoveAll: %v\n", err)
|
||||
os.Exit(1)
|
||||
log.Error("Failed to remove indexer path: %v", err)
|
||||
}
|
||||
if err = util.RemoveAll(setting.Indexer.RepoPath); err != nil {
|
||||
fmt.Printf("Unable to remove repo indexer: %v\n", err)
|
||||
os.Exit(1)
|
||||
log.Error("Failed to remove indexer path: %v", err)
|
||||
}
|
||||
return exitCode
|
||||
}
|
||||
|
||||
os.Exit(exitCode)
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(testMain(m))
|
||||
}
|
||||
|
||||
type TestSession struct {
|
||||
@@ -225,16 +226,11 @@ func loginUser(t testing.TB, userName string) *TestSession {
|
||||
|
||||
func loginUserWithPassword(t testing.TB, userName, password string) *TestSession {
|
||||
t.Helper()
|
||||
req := NewRequest(t, "GET", "/user/login")
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
req = NewRequestWithValues(t, "POST", "/user/login", map[string]string{
|
||||
"_csrf": doc.GetCSRF(),
|
||||
req := NewRequestWithValues(t, "POST", "/user/login", map[string]string{
|
||||
"user_name": userName,
|
||||
"password": password,
|
||||
})
|
||||
resp = MakeRequest(t, req, http.StatusSeeOther)
|
||||
resp := MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
ch := http.Header{}
|
||||
ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";"))
|
||||
@@ -250,14 +246,13 @@ func loginUserWithPassword(t testing.TB, userName, password string) *TestSession
|
||||
}
|
||||
|
||||
// token has to be unique this counter take care of
|
||||
var tokenCounter int64
|
||||
var tokenCounter atomic.Int64
|
||||
|
||||
// getTokenForLoggedInUser returns a token for a logged-in user.
|
||||
func getTokenForLoggedInUser(t testing.TB, session *TestSession, scopes ...auth.AccessTokenScope) string {
|
||||
t.Helper()
|
||||
urlValues := url.Values{}
|
||||
urlValues.Add("_csrf", GetUserCSRFToken(t, session))
|
||||
urlValues.Add("name", fmt.Sprintf("api-testing-token-%d", atomic.AddInt64(&tokenCounter, 1)))
|
||||
urlValues.Add("name", fmt.Sprintf("api-testing-token-%d", tokenCounter.Add(1)))
|
||||
for _, scope := range scopes {
|
||||
urlValues.Add("scope-dummy", string(scope)) // it only needs to start with "scope-" to be accepted
|
||||
}
|
||||
@@ -332,9 +327,10 @@ func NewRequestWithBody(t testing.TB, method, urlStr string, body io.Reader) *Re
|
||||
urlStr = "/" + urlStr
|
||||
}
|
||||
req, err := http.NewRequest(method, urlStr, body)
|
||||
assert.NoError(t, err)
|
||||
req.RequestURI = urlStr
|
||||
|
||||
require.NoError(t, err)
|
||||
if req.URL.User != nil {
|
||||
req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(req.URL.User.String())))
|
||||
}
|
||||
return &RequestWrapper{req}
|
||||
}
|
||||
|
||||
@@ -347,6 +343,10 @@ func MakeRequest(t testing.TB, rw *RequestWrapper, expectedStatus int) *httptest
|
||||
if req.RemoteAddr == "" {
|
||||
req.RemoteAddr = "test-mock:12345"
|
||||
}
|
||||
// Ensure unknown contentLength is seen as -1
|
||||
if req.Body != nil && req.ContentLength == 0 {
|
||||
req.ContentLength = -1
|
||||
}
|
||||
testWebRoutes.ServeHTTP(recorder, req)
|
||||
if expectedStatus != NoExpectedStatus {
|
||||
if expectedStatus != recorder.Code {
|
||||
@@ -410,11 +410,13 @@ func logUnexpectedResponse(t testing.TB, recorder *httptest.ResponseRecorder) {
|
||||
}
|
||||
}
|
||||
|
||||
func DecodeJSON(t testing.TB, resp *httptest.ResponseRecorder, v any) {
|
||||
func DecodeJSON[T any](t testing.TB, resp *httptest.ResponseRecorder, v T) (ret T) {
|
||||
t.Helper()
|
||||
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
require.NoError(t, decoder.Decode(v))
|
||||
// FIXME: JSON-KEY-CASE: for testing purpose only, because many structs don't provide `json` tags, they just use capitalized field names
|
||||
decoder := json.NewDecoderCaseInsensitive(resp.Body)
|
||||
require.NoError(t, decoder.Decode(&v))
|
||||
return v
|
||||
}
|
||||
|
||||
func VerifyJSONSchema(t testing.TB, resp *httptest.ResponseRecorder, schemaFile string) {
|
||||
@@ -435,20 +437,3 @@ func VerifyJSONSchema(t testing.TB, resp *httptest.ResponseRecorder, schemaFile
|
||||
assert.Empty(t, result.Errors())
|
||||
assert.True(t, result.Valid())
|
||||
}
|
||||
|
||||
// GetUserCSRFToken returns CSRF token for current user
|
||||
func GetUserCSRFToken(t testing.TB, session *TestSession) string {
|
||||
t.Helper()
|
||||
cookie := session.GetSiteCookie("_csrf")
|
||||
require.NotEmpty(t, cookie)
|
||||
return cookie
|
||||
}
|
||||
|
||||
// GetUserCSRFToken returns CSRF token for anonymous user (not logged in)
|
||||
func GetAnonymousCSRFToken(t testing.TB, session *TestSession) string {
|
||||
t.Helper()
|
||||
resp := session.MakeRequest(t, NewRequest(t, "GET", "/user/login"), http.StatusOK)
|
||||
csrfToken := NewHTMLParser(t, resp.Body).GetCSRF()
|
||||
require.NotEmpty(t, csrfToken)
|
||||
return csrfToken
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user