mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-24 19:08:36 +02:00
feat: Merge the main branch
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
import {env} from 'node:process';
|
||||
import {expect, test} from '@playwright/test';
|
||||
import {apiCreateRepo, apiCreateIssue, assertNoJsError, randomString} from './utils.ts';
|
||||
|
||||
test('mermaid diagram in issue', async ({page, request}) => {
|
||||
const repoName = `e2e-mermaid-${randomString(8)}`;
|
||||
const owner = env.GITEA_TEST_E2E_USER;
|
||||
await apiCreateRepo(request, {name: repoName});
|
||||
const body = '```mermaid\nflowchart LR\n Alpha --> Beta\n Beta --> Gamma\n```\n';
|
||||
const {index} = await apiCreateIssue(request, {owner, repo: repoName, title: 'mermaid test', body});
|
||||
await page.goto(`/${owner}/${repoName}/issues/${index}`);
|
||||
|
||||
const svg = page.frameLocator('iframe.markup-content-iframe').locator('svg');
|
||||
await expect(svg).toContainText(/Alpha[\s\S]*Beta[\s\S]*Gamma/);
|
||||
|
||||
await assertNoJsError(page);
|
||||
});
|
||||
+22
-1
@@ -1,5 +1,5 @@
|
||||
import {test, expect} from '@playwright/test';
|
||||
import {login, apiDeleteOrg, randomString} from './utils.ts';
|
||||
import {login, apiCreateOrg, apiCreateTeam, apiCreateUser, apiDeleteOrg, randomString} from './utils.ts';
|
||||
|
||||
test('create an organization', async ({page}) => {
|
||||
const orgName = `e2e-org-${randomString(8)}`;
|
||||
@@ -11,3 +11,24 @@ test('create an organization', async ({page}) => {
|
||||
// delete via API because of issues related to form-fetch-action
|
||||
await apiDeleteOrg(page.request, orgName);
|
||||
});
|
||||
|
||||
test('add team member search', async ({page, request}) => {
|
||||
const orgName = `team-add-${randomString(8)}`;
|
||||
const teamName = `team-add-${randomString(8)}`;
|
||||
const userName = `team-add-${randomString(8)}`;
|
||||
|
||||
await Promise.all([
|
||||
(async () => {
|
||||
await apiCreateOrg(request, orgName);
|
||||
await apiCreateTeam(request, orgName, teamName);
|
||||
})(),
|
||||
apiCreateUser(request, userName),
|
||||
login(page),
|
||||
]);
|
||||
|
||||
await page.goto(`/org/${orgName}/teams/${teamName}`);
|
||||
const input = page.locator('#search-user-box input.prompt');
|
||||
await input.fill(userName.slice(-6));
|
||||
const result = page.locator('#search-user-box .results .result').first();
|
||||
await expect(result).toContainText(userName);
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ test('toggle issue reactions', async ({page, request}) => {
|
||||
]);
|
||||
await page.goto(`/${owner}/${repoName}/issues/1`);
|
||||
|
||||
const issueComment = page.locator('.timeline-item.comment.first');
|
||||
const issueComment = page.locator('.timeline-item.comment.issue-content-comment');
|
||||
|
||||
const reactionPicker = issueComment.locator('.select-reaction');
|
||||
await reactionPicker.click();
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import {env} from 'node:process';
|
||||
import {test, expect} from '@playwright/test';
|
||||
import {apiCreateRepo, apiCreateUser, login, randomString} from './utils.ts';
|
||||
|
||||
test('add collaborator search', async ({page, request}) => {
|
||||
const userName = `repo-collab-${randomString(8)}`;
|
||||
const repoName = `repo-collab-${randomString(8)}`;
|
||||
|
||||
await Promise.all([
|
||||
apiCreateUser(request, userName),
|
||||
apiCreateRepo(request, {name: repoName, autoInit: false}),
|
||||
login(page),
|
||||
]);
|
||||
|
||||
await page.goto(`/${env.GITEA_TEST_E2E_USER}/${repoName}/settings/collaboration`);
|
||||
const input = page.locator('#search-user-box input.prompt');
|
||||
await input.fill(userName.slice(-6));
|
||||
const result = page.locator('#search-user-box .results .result').first();
|
||||
await expect(result).toContainText(userName);
|
||||
await result.click();
|
||||
await expect(input).toHaveValue(userName);
|
||||
await page.getByRole('button', {name: 'Add Collaborator'}).click();
|
||||
await expect(page.locator('body')).toContainText(userName);
|
||||
});
|
||||
@@ -47,6 +47,20 @@ export async function apiCreateRepo(requestContext: APIRequestContext, {name, au
|
||||
}), 'apiCreateRepo');
|
||||
}
|
||||
|
||||
export async function apiCreateOrg(requestContext: APIRequestContext, name: string, {headers}: {headers?: Record<string, string>} = {}) {
|
||||
await apiRetry(() => requestContext.post(`${baseUrl()}/api/v1/orgs`, {
|
||||
headers: headers || apiHeaders(),
|
||||
data: {username: name},
|
||||
}), 'apiCreateOrg');
|
||||
}
|
||||
|
||||
export async function apiCreateTeam(requestContext: APIRequestContext, org: string, name: string, {permission = 'read', units = ['repo.code'], headers}: {permission?: string; units?: Array<string>; headers?: Record<string, string>} = {}) {
|
||||
await apiRetry(() => requestContext.post(`${baseUrl()}/api/v1/orgs/${org}/teams`, {
|
||||
headers: headers || apiHeaders(),
|
||||
data: {name, permission, units},
|
||||
}), 'apiCreateTeam');
|
||||
}
|
||||
|
||||
export async function apiStartStopwatch(requestContext: APIRequestContext, owner: string, repo: string, issueIndex: number, {headers}: {headers?: Record<string, string>} = {}) {
|
||||
await apiRetry(() => requestContext.post(`${baseUrl()}/api/v1/repos/${owner}/${repo}/issues/${issueIndex}/stopwatch/start`, {
|
||||
headers: headers || apiHeaders(),
|
||||
|
||||
@@ -1560,6 +1560,9 @@ jobs:
|
||||
// run2 is blocked because it is blocked by workflow1's concurrency group "test-group"
|
||||
assert.Equal(t, actions_model.StatusBlocked, run2.Status)
|
||||
|
||||
// complete wf1-job1
|
||||
runner.execTask(t, w1j1Task, &mockTaskOutcome{result: runnerv1.Result_RESULT_SUCCESS})
|
||||
|
||||
// mock time
|
||||
fakeNow := now.Add(setting.Actions.AbandonedJobTimeout)
|
||||
timeutil.MockSet(fakeNow)
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
}
|
||||
@@ -160,6 +160,10 @@ func testActionsRouteForLegacyIndexBasedURL(t *testing.T) {
|
||||
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)
|
||||
@@ -182,11 +186,12 @@ func testActionsRouteForLegacyIndexBasedURL(t *testing.T) {
|
||||
targetAmbiguousLegacyJob := ambiguousLegacyJobs[int(ambiguousIDJob.ID)]
|
||||
|
||||
insertBeansWithExplicitIDs(t, "action_run",
|
||||
smallIDRun, otherSmallRun, normalRun, ambiguousIDRun, ambiguousLegacyRun, collisionRun,
|
||||
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) {
|
||||
@@ -220,6 +225,9 @@ func testActionsRouteForLegacyIndexBasedURL(t *testing.T) {
|
||||
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) {
|
||||
|
||||
@@ -1801,7 +1801,7 @@ jobs:
|
||||
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)
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
runner.fetchNoTask(t)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -398,7 +398,7 @@ func TestActionsArtifactV4UploadSingleFileWithChunksOutOfOrder(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Actions.ArtifactStorage.AzureBlobConfig.ServeDirect, entry.serveDirect)()
|
||||
default:
|
||||
if entry.serveDirect {
|
||||
t.Skip()
|
||||
t.Skip("for non-serve-direct only")
|
||||
}
|
||||
}
|
||||
// acquire artifact upload url
|
||||
@@ -529,7 +529,7 @@ func TestActionsArtifactV4DownloadSingle(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Actions.ArtifactStorage.MinioConfig.ServeDirect, entry.ServeDirect)()
|
||||
default:
|
||||
if entry.ServeDirect {
|
||||
t.Skip()
|
||||
t.Skip("for non-serve-direct only")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -327,6 +327,30 @@ func testAPIActionsListUserWorkflows(t *testing.T) {
|
||||
assert.NotEmpty(t, job.HTMLURL, "html_url should be populated via batch-loaded repo")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("JobsDefaultOrderAsc", func(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/api/v1/user/actions/jobs").AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
jobs := DecodeJSON(t, resp, &api.ActionWorkflowJobsResponse{})
|
||||
|
||||
assert.GreaterOrEqual(t, len(jobs.Entries), 2, "need at least 2 jobs to verify ordering")
|
||||
for i := 1; i < len(jobs.Entries); i++ {
|
||||
assert.Less(t, jobs.Entries[i-1].ID, jobs.Entries[i].ID,
|
||||
"jobs should be ordered by ID ascending by default")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("JobsOrderedByIDDesc", func(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/api/v1/user/actions/jobs?sort=id&order=desc").AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
jobs := DecodeJSON(t, resp, &api.ActionWorkflowJobsResponse{})
|
||||
|
||||
assert.GreaterOrEqual(t, len(jobs.Entries), 2, "need at least 2 jobs to verify ordering")
|
||||
for i := 1; i < len(jobs.Entries); i++ {
|
||||
assert.Greater(t, jobs.Entries[i-1].ID, jobs.Entries[i].ID,
|
||||
"jobs should be ordered by ID descending")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func testAPIActionsListRepoWorkflows(t *testing.T) {
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"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/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -335,11 +336,7 @@ func TestAPICron(t *testing.T) {
|
||||
|
||||
func TestAPICreateUser_NotAllowedEmailDomain(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("example.org")}
|
||||
defer func() {
|
||||
setting.Service.EmailDomainAllowList = []glob.Glob{}
|
||||
}()
|
||||
defer test.MockVariableValue(&setting.Service.EmailDomainAllowList, []glob.Glob{glob.MustCompile("example.org")})()
|
||||
|
||||
adminUsername := "user1"
|
||||
token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
|
||||
@@ -360,11 +357,7 @@ func TestAPICreateUser_NotAllowedEmailDomain(t *testing.T) {
|
||||
|
||||
func TestAPIEditUser_NotAllowedEmailDomain(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("example.org")}
|
||||
defer func() {
|
||||
setting.Service.EmailDomainAllowList = []glob.Glob{}
|
||||
}()
|
||||
defer test.MockVariableValue(&setting.Service.EmailDomainAllowList, []glob.Glob{glob.MustCompile("example.org")})()
|
||||
|
||||
adminUsername := "user1"
|
||||
token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
|
||||
|
||||
@@ -356,7 +356,11 @@ func testAPIRenameBranch(t *testing.T, doerName, ownerName, repoName, from, to s
|
||||
|
||||
func TestAPIBranchProtection(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
t.Run("Basic", testAPIBranchProtectionBasic)
|
||||
t.Run("BypassAllowlistValidation", testAPIBranchProtectionBypassAllowlistValidation)
|
||||
}
|
||||
|
||||
func testAPIBranchProtectionBasic(t *testing.T) {
|
||||
// Can create branch protection on branch that not exist
|
||||
testAPICreateBranchProtection(t, "non-existing/branch", 1, http.StatusCreated)
|
||||
testAPIGetBranchProtection(t, "non-existing/branch", http.StatusOK)
|
||||
@@ -406,6 +410,35 @@ func TestAPIBranchProtection(t *testing.T) {
|
||||
testAPIDeleteBranch(t, "no-such-branch", http.StatusNotFound) // non-existing branch, not exist in git or DB
|
||||
}
|
||||
|
||||
func testAPIBranchProtectionBypassAllowlistValidation(t *testing.T) {
|
||||
token := getUserToken(t, "user2", auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
t.Run("IgnoreInvalidBypassUsernamesWhenDisabled", func(t *testing.T) {
|
||||
ruleName := "bypass-disabled-invalid-user"
|
||||
req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/branch_protections", &api.CreateBranchProtectionOption{
|
||||
RuleName: ruleName,
|
||||
EnableBypassAllowlist: false,
|
||||
BypassAllowlistUsernames: []string{"nonexistent-user"},
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
testAPIDeleteBranchProtection(t, ruleName, http.StatusNoContent)
|
||||
})
|
||||
|
||||
t.Run("IgnoreInvalidBypassTeamsWhenDisabled", func(t *testing.T) {
|
||||
ruleName := "bypass-disabled-invalid-team"
|
||||
req := NewRequestWithJSON(t, "POST", "/api/v1/repos/org3/repo3/branch_protections", &api.CreateBranchProtectionOption{
|
||||
RuleName: ruleName,
|
||||
EnableBypassAllowlist: false,
|
||||
BypassAllowlistTeams: []string{"nonexistent-team"},
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
deleteReq := NewRequestf(t, "DELETE", "/api/v1/repos/org3/repo3/branch_protections/%s", ruleName).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, deleteReq, http.StatusNoContent)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPICreateBranchWithSyncBranches(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
|
||||
@@ -21,8 +21,9 @@ func TestAPIListGitignoresTemplates(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/api/v1/gitignore/templates")
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// This tests if the API returns a list of strings
|
||||
DecodeJSON(t, resp, []string{})
|
||||
templateList := DecodeJSON(t, resp, []string{}) // this is a very long list
|
||||
assert.Contains(t, templateList, "C++")
|
||||
assert.Contains(t, templateList, "Go")
|
||||
}
|
||||
|
||||
func TestAPIGetGitignoreTemplateInfo(t *testing.T) {
|
||||
|
||||
@@ -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"
|
||||
@@ -176,3 +177,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +39,7 @@ func TestAPIIssueSubscriptions(t *testing.T) {
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/subscriptions/check", issueRepo.OwnerName, issueRepo.Name, issue.Index)).
|
||||
AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
wi := new(api.WatchInfo)
|
||||
DecodeJSON(t, resp, wi)
|
||||
wi := DecodeJSON(t, resp, &api.WatchInfo{})
|
||||
|
||||
assert.Equal(t, isWatching, wi.Subscribed)
|
||||
assert.Equal(t, !isWatching, wi.Ignored)
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -49,3 +51,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)
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ 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) {
|
||||
@@ -149,16 +149,16 @@ func testAPICreateIssue(t *testing.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,
|
||||
// HINT: 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.
|
||||
// because the "unlock_notify_wait" never returns and the internal lock never gets released.
|
||||
//
|
||||
// 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) } }
|
||||
// Usually the test gets stuck in fewer than 10 iterations without this "sleep".
|
||||
time.Sleep(time.Second)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
const body, title = "apiTestBody", "apiTestTitle"
|
||||
|
||||
@@ -171,9 +171,8 @@ func testAPICreateIssueParallel(t *testing.T) {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := range 10 {
|
||||
wg.Add(1)
|
||||
go func(parentT *testing.T, i int) {
|
||||
parentT.Run(fmt.Sprintf("ParallelCreateIssue_%d", i), func(t *testing.T) {
|
||||
wg.Go(func() {
|
||||
t.Run(fmt.Sprintf("ParallelCreateIssue_%d", i), func(t *testing.T) {
|
||||
newTitle := title + strconv.Itoa(i)
|
||||
newBody := body + strconv.Itoa(i)
|
||||
req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateIssueOption{
|
||||
@@ -192,10 +191,8 @@ func testAPICreateIssueParallel(t *testing.T) {
|
||||
Content: newBody,
|
||||
Title: newTitle,
|
||||
})
|
||||
|
||||
wg.Done()
|
||||
})
|
||||
}(t, i)
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
@@ -513,15 +510,14 @@ func testAPIIssueProjects(t *testing.T) {
|
||||
Projects: []int64{1},
|
||||
}).AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusCreated)
|
||||
var apiIssue api.Issue
|
||||
DecodeJSON(t, resp, &apiIssue)
|
||||
apiIssue := DecodeJSON(t, resp, &api.Issue{})
|
||||
assert.Len(t, apiIssue.Projects, 1)
|
||||
assert.EqualValues(t, 1, apiIssue.Projects[0].ID)
|
||||
|
||||
// Get issue should include projects
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/%d", urlStr, apiIssue.Index)).AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &apiIssue)
|
||||
apiIssue = DecodeJSON(t, resp, &api.Issue{})
|
||||
assert.Len(t, apiIssue.Projects, 1)
|
||||
assert.EqualValues(t, 1, apiIssue.Projects[0].ID)
|
||||
|
||||
@@ -531,7 +527,7 @@ func testAPIIssueProjects(t *testing.T) {
|
||||
Projects: &emptyProjects,
|
||||
}).AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusCreated)
|
||||
DecodeJSON(t, resp, &apiIssue)
|
||||
apiIssue = DecodeJSON(t, resp, &api.Issue{})
|
||||
assert.Empty(t, apiIssue.Projects)
|
||||
|
||||
// Edit issue to add project back
|
||||
@@ -540,7 +536,7 @@ func testAPIIssueProjects(t *testing.T) {
|
||||
Projects: &projects,
|
||||
}).AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusCreated)
|
||||
DecodeJSON(t, resp, &apiIssue)
|
||||
apiIssue = DecodeJSON(t, resp, &api.Issue{})
|
||||
assert.Len(t, apiIssue.Projects, 1)
|
||||
assert.EqualValues(t, 1, apiIssue.Projects[0].ID)
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/modules/options"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
@@ -22,8 +23,12 @@ func TestAPIListLicenseTemplates(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/api/v1/licenses")
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// This tests if the API returns a list of strings
|
||||
DecodeJSON(t, resp, []api.LicensesTemplateListEntry{})
|
||||
licenseList := DecodeJSON(t, resp, []api.LicensesTemplateListEntry{})
|
||||
assert.Contains(t, licenseList, api.LicensesTemplateListEntry{
|
||||
Key: "MIT",
|
||||
Name: "MIT",
|
||||
URL: setting.AppURL + "api/v1/licenses/MIT",
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIGetLicenseTemplateInfo(t *testing.T) {
|
||||
|
||||
@@ -209,3 +209,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)
|
||||
}
|
||||
|
||||
@@ -27,6 +27,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"
|
||||
@@ -243,5 +245,83 @@ 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 = DecodeJSON(t, resp, &composer.PackageMetadataResponse{})
|
||||
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 = DecodeJSON(t, resp, &composer.PackageMetadataResponse{})
|
||||
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, bytes.NewReader(listResp.Body.Bytes()))
|
||||
assert.Equal(t, 0, listDoc.Find(".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, bytes.NewReader(viewResp.Body.Bytes()))
|
||||
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, bytes.NewReader(privateListResp.Body.Bytes()))
|
||||
assert.Equal(t, 1, privateListDoc.Find(".item-title .ui.basic.label").Length())
|
||||
assert.Equal(t, "Private", privateListDoc.Find(".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, bytes.NewReader(privateViewResp.Body.Bytes()))
|
||||
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())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -766,20 +766,16 @@ func TestPackageContainer(t *testing.T) {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := range 10 {
|
||||
wg.Add(1)
|
||||
|
||||
content := []byte{byte(i)}
|
||||
digest := fmt.Sprintf("sha256:%x", sha256.Sum256(content))
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
wg.Go(func() {
|
||||
req := NewRequestWithBody(t, "POST", fmt.Sprintf("%s/blobs/uploads?digest=%s", url, digest), bytes.NewReader(content)).
|
||||
AddTokenAuth(userToken)
|
||||
resp := MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
assert.Equal(t, digest, resp.Header().Get("Docker-Content-Digest"))
|
||||
}()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
})
|
||||
|
||||
@@ -321,11 +321,9 @@ func TestPackageMavenConcurrent(t *testing.T) {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := range 10 {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
wg.Go(func() {
|
||||
putFile(t, fmt.Sprintf("/%s/%s.jar", packageVersion, strconv.Itoa(i)), "test", http.StatusCreated)
|
||||
wg.Done()
|
||||
}(i)
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -556,9 +556,7 @@ func testAPIPullReviewCommentReply(t *testing.T) {
|
||||
// happy path
|
||||
req := NewRequestWithJSON(t, http.MethodPost, url, &api.CreatePullReviewCommentReplyOptions{Body: "the reply"}).AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
var reply api.PullReviewComment
|
||||
DecodeJSON(t, resp, &reply)
|
||||
reply := DecodeJSON(t, resp, &api.PullReviewComment{})
|
||||
assert.Equal(t, "the reply", reply.Body)
|
||||
assert.Equal(t, parent.ReviewID, reply.ReviewID)
|
||||
assert.Equal(t, "README.md", reply.Path)
|
||||
|
||||
@@ -155,8 +155,7 @@ func TestAPIViewPullsByBaseHead(t *testing.T) {
|
||||
AddTokenAuth(ctx.Token)
|
||||
resp := ctx.Session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
pull := &api.PullRequest{}
|
||||
DecodeJSON(t, resp, pull)
|
||||
pull := DecodeJSON(t, resp, &api.PullRequest{})
|
||||
assert.EqualValues(t, 3, pull.Index)
|
||||
assert.EqualValues(t, 2, pull.ID)
|
||||
|
||||
@@ -394,10 +393,8 @@ func TestAPICreatePullWithFieldsSuccess(t *testing.T) {
|
||||
|
||||
req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), opts).
|
||||
AddTokenAuth(token)
|
||||
|
||||
res := MakeRequest(t, req, http.StatusCreated)
|
||||
pull := new(api.PullRequest)
|
||||
DecodeJSON(t, res, pull)
|
||||
resp := MakeRequest(t, req, http.StatusCreated)
|
||||
pull := DecodeJSON(t, resp, &api.PullRequest{})
|
||||
|
||||
assert.NotNil(t, pull.Milestone)
|
||||
assert.Equal(t, opts.Milestone, pull.Milestone.ID)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
mirror_service "code.gitea.io/gitea/services/mirror"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -69,6 +70,9 @@ func getRepoEditOptionFromRepo(repo *repo_model.Repository) *api.EditRepoOption
|
||||
allowRebaseMerge := false
|
||||
allowSquash := false
|
||||
allowFastForwardOnly := false
|
||||
allowMergeUpdate := false
|
||||
allowRebaseUpdate := false
|
||||
defaultUpdateStyle := string(repo_model.UpdateStyleMerge)
|
||||
if unit, err := repo.GetUnit(ctx, unit_model.TypePullRequests); err == nil {
|
||||
config := unit.PullRequestsConfig()
|
||||
hasPullRequests = true
|
||||
@@ -78,6 +82,9 @@ func getRepoEditOptionFromRepo(repo *repo_model.Repository) *api.EditRepoOption
|
||||
allowRebaseMerge = config.AllowRebaseMerge
|
||||
allowSquash = config.AllowSquash
|
||||
allowFastForwardOnly = config.AllowFastForwardOnly
|
||||
allowMergeUpdate = config.AllowMergeUpdate
|
||||
allowRebaseUpdate = config.AllowRebaseUpdate
|
||||
defaultUpdateStyle = string(config.DefaultUpdateStyle)
|
||||
}
|
||||
archived := repo.IsArchived
|
||||
hasProjects := false
|
||||
@@ -122,6 +129,9 @@ func getRepoEditOptionFromRepo(repo *repo_model.Repository) *api.EditRepoOption
|
||||
AllowRebaseMerge: &allowRebaseMerge,
|
||||
AllowSquash: &allowSquash,
|
||||
AllowFastForwardOnly: &allowFastForwardOnly,
|
||||
AllowMergeUpdate: &allowMergeUpdate,
|
||||
AllowRebaseUpdate: &allowRebaseUpdate,
|
||||
DefaultUpdateStyle: &defaultUpdateStyle,
|
||||
Archived: &archived,
|
||||
}
|
||||
}
|
||||
@@ -148,6 +158,9 @@ func getNewRepoEditOption(opts *api.EditRepoOption) *api.EditRepoOption {
|
||||
allowRebase := !*opts.AllowRebase
|
||||
allowRebaseMerge := !*opts.AllowRebaseMerge
|
||||
allowSquash := !*opts.AllowSquash
|
||||
allowMergeUpdate := false
|
||||
allowRebaseUpdate := true
|
||||
defaultUpdateStyle := string(repo_model.UpdateStyleRebase)
|
||||
archived := !*opts.Archived
|
||||
|
||||
return &api.EditRepoOption{
|
||||
@@ -169,6 +182,9 @@ func getNewRepoEditOption(opts *api.EditRepoOption) *api.EditRepoOption {
|
||||
AllowRebase: &allowRebase,
|
||||
AllowRebaseMerge: &allowRebaseMerge,
|
||||
AllowSquash: &allowSquash,
|
||||
AllowMergeUpdate: &allowMergeUpdate,
|
||||
AllowRebaseUpdate: &allowRebaseUpdate,
|
||||
DefaultUpdateStyle: &defaultUpdateStyle,
|
||||
Archived: &archived,
|
||||
}
|
||||
}
|
||||
@@ -488,3 +504,31 @@ func TestAPIRepoEdit(t *testing.T) {
|
||||
assert.Equal(t, token, password)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIRepoEditPullUpdateSettingsValidation(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
|
||||
session := loginUser(t, user2.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
repoURL := fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo1.Name)
|
||||
|
||||
allowMergeUpdate := false
|
||||
allowRebaseUpdate := false
|
||||
req := NewRequestWithJSON(t, "PATCH", repoURL, &api.EditRepoOption{
|
||||
AllowMergeUpdate: &allowMergeUpdate,
|
||||
AllowRebaseUpdate: &allowRebaseUpdate,
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusUnprocessableEntity)
|
||||
|
||||
allowRebaseUpdate = true
|
||||
defaultUpdateStyle := string(repo_model.UpdateStyleMerge)
|
||||
req = NewRequestWithJSON(t, "PATCH", repoURL, &api.EditRepoOption{
|
||||
AllowMergeUpdate: &allowMergeUpdate,
|
||||
AllowRebaseUpdate: &allowRebaseUpdate,
|
||||
DefaultUpdateStyle: &defaultUpdateStyle,
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusUnprocessableEntity)
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ func TestAPIGetRawFileOrLFS(t *testing.T) {
|
||||
lfs := lfsCommitAndPushTest(t, dstPath, testFileSizeSmall)[0]
|
||||
|
||||
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)
|
||||
respLFS := MakeRequest(t, reqLFS, http.StatusOK)
|
||||
assert.Equal(t, testFileSizeSmall, respLFS.Body.Len())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/lfs"
|
||||
"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"
|
||||
@@ -22,7 +23,7 @@ import (
|
||||
|
||||
func TestAPILFSLocksNotStarted(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
setting.LFS.StartServer = false
|
||||
defer test.MockVariableValue(&setting.LFS.StartServer, false)()
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
|
||||
@@ -38,7 +39,7 @@ func TestAPILFSLocksNotStarted(t *testing.T) {
|
||||
|
||||
func TestAPILFSLocksNotLogin(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
setting.LFS.StartServer = true
|
||||
defer test.MockVariableValue(&setting.LFS.StartServer, true)()
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
|
||||
@@ -51,7 +52,7 @@ func TestAPILFSLocksNotLogin(t *testing.T) {
|
||||
|
||||
func TestAPILFSLocksLogged(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
setting.LFS.StartServer = true
|
||||
defer test.MockVariableValue(&setting.LFS.StartServer, true)()
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // in org 3
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // in org 3
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/lfs"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/services/migrations"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
@@ -22,11 +23,8 @@ import (
|
||||
|
||||
func TestAPIRepoLFSMigrateLocal(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
oldImportLocalPaths := setting.ImportLocalPaths
|
||||
oldAllowLocalNetworks := setting.Migrations.AllowLocalNetworks
|
||||
setting.ImportLocalPaths = true
|
||||
setting.Migrations.AllowLocalNetworks = true
|
||||
defer test.MockVariableValue(&setting.ImportLocalPaths, true)()
|
||||
defer test.MockVariableValue(&setting.Migrations.AllowLocalNetworks, true)()
|
||||
assert.NoError(t, migrations.Init())
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
@@ -47,8 +45,4 @@ func TestAPIRepoLFSMigrateLocal(t *testing.T) {
|
||||
assert.True(t, ok)
|
||||
ok, _ = store.Verify(lfs.Pointer{Oid: "d6f175817f886ec6fbbc1515326465fa96c3bfd54a4ea06cfd6dbbd8340e0152", Size: 6})
|
||||
assert.True(t, ok)
|
||||
|
||||
setting.ImportLocalPaths = oldImportLocalPaths
|
||||
setting.Migrations.AllowLocalNetworks = oldAllowLocalNetworks
|
||||
assert.NoError(t, migrations.Init()) // reset old migration settings
|
||||
}
|
||||
|
||||
@@ -28,8 +28,7 @@ import (
|
||||
|
||||
func TestAPILFSNotStarted(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
setting.LFS.StartServer = false
|
||||
defer test.MockVariableValue(&setting.LFS.StartServer, false)()
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
@@ -48,8 +47,7 @@ func TestAPILFSNotStarted(t *testing.T) {
|
||||
|
||||
func TestAPILFSMediaType(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
setting.LFS.StartServer = true
|
||||
defer test.MockVariableValue(&setting.LFS.StartServer, true)()
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
@@ -72,8 +70,7 @@ func createLFSTestRepository(t *testing.T, repoName string) *repo_model.Reposito
|
||||
|
||||
func TestAPILFSBatch(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
setting.LFS.StartServer = true
|
||||
defer test.MockVariableValue(&setting.LFS.StartServer, true)()
|
||||
|
||||
repo := createLFSTestRepository(t, "lfs-batch-repo")
|
||||
|
||||
@@ -326,8 +323,7 @@ func TestAPILFSBatch(t *testing.T) {
|
||||
|
||||
func TestAPILFSUpload(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
setting.LFS.StartServer = true
|
||||
defer test.MockVariableValue(&setting.LFS.StartServer, true)()
|
||||
|
||||
repo := createLFSTestRepository(t, "lfs-upload-repo")
|
||||
oid := storeObjectInRepo(t, repo.ID, "dummy3")
|
||||
@@ -428,8 +424,7 @@ func TestAPILFSUpload(t *testing.T) {
|
||||
|
||||
func TestAPILFSVerify(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
setting.LFS.StartServer = true
|
||||
defer test.MockVariableValue(&setting.LFS.StartServer, true)()
|
||||
|
||||
repo := createLFSTestRepository(t, "lfs-verify-repo")
|
||||
oid := storeObjectInRepo(t, repo.ID, "dummy3")
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAPIRepoTags(t *testing.T) {
|
||||
@@ -35,24 +36,22 @@ func TestAPIRepoTags(t *testing.T) {
|
||||
|
||||
assert.Len(t, tags, 1)
|
||||
assert.Equal(t, "v1.1", tags[0].Name)
|
||||
assert.Equal(t, "Initial commit", tags[0].Message)
|
||||
assert.Equal(t, "Initial commit\n", tags[0].Message)
|
||||
assert.Equal(t, "65f1bf27bc3bf70f64657658635e66094edbcb4d", tags[0].Commit.SHA)
|
||||
assert.Equal(t, setting.AppURL+"api/v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d", tags[0].Commit.URL)
|
||||
assert.Equal(t, setting.AppURL+"user2/repo1/archive/v1.1.zip", tags[0].ZipballURL)
|
||||
assert.Equal(t, setting.AppURL+"user2/repo1/archive/v1.1.tar.gz", tags[0].TarballURL)
|
||||
|
||||
newTag := createNewTagUsingAPI(t, token, user.Name, repoName, "gitea/22", "", "nice!\nand some text")
|
||||
assert.Equal(t, "nice!\nand some text\n", newTag.Message) // git message standard: there will always be a newline at the end of the message
|
||||
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
tags = DecodeJSON(t, resp, []*api.Tag{})
|
||||
assert.Len(t, tags, 2)
|
||||
for _, tag := range tags {
|
||||
if tag.Name != "v1.1" {
|
||||
assert.Equal(t, newTag.Name, tag.Name)
|
||||
assert.Equal(t, newTag.Message, tag.Message)
|
||||
assert.Equal(t, "nice!\nand some text", tag.Message)
|
||||
assert.Equal(t, newTag.Commit.SHA, tag.Commit.SHA)
|
||||
}
|
||||
}
|
||||
require.Len(t, tags, 2)
|
||||
respTag := tags[0]
|
||||
assert.Equal(t, newTag.Name, respTag.Name)
|
||||
assert.Equal(t, newTag.Message, respTag.Message)
|
||||
assert.Equal(t, newTag.Commit.SHA, respTag.Commit.SHA)
|
||||
|
||||
// get created tag
|
||||
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/tags/%s", user.Name, repoName, newTag.Name).
|
||||
|
||||
@@ -17,10 +17,13 @@ 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/services/migrations"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAPIUserReposNotLogin(t *testing.T) {
|
||||
@@ -60,11 +63,7 @@ func TestAPISearchRepo(t *testing.T) {
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 20})
|
||||
orgUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17})
|
||||
|
||||
oldAPIDefaultNum := setting.API.DefaultPagingNum
|
||||
defer func() {
|
||||
setting.API.DefaultPagingNum = oldAPIDefaultNum
|
||||
}()
|
||||
setting.API.DefaultPagingNum = 10
|
||||
defer test.MockVariableValue(&setting.API.DefaultPagingNum, 10)()
|
||||
|
||||
// Map of expected results, where key is user for login
|
||||
type expectedResults map[*user_model.User]struct {
|
||||
@@ -367,6 +366,9 @@ func TestAPIRepoMigrate(t *testing.T) {
|
||||
}
|
||||
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
defer test.MockVariableValue(&setting.Migrations.AllowLocalNetworks, false)()
|
||||
require.NoError(t, migrations.Init())
|
||||
|
||||
for _, testCase := range testCases {
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: testCase.ctxUserID})
|
||||
session := loginUser(t, user.Name)
|
||||
@@ -536,7 +538,6 @@ func TestAPIRepoTransfer(t *testing.T) {
|
||||
session := loginUser(t, user.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
repoName := "moveME"
|
||||
apiRepo := new(api.Repository)
|
||||
req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{
|
||||
Name: repoName,
|
||||
Description: "repo move around",
|
||||
@@ -545,7 +546,7 @@ func TestAPIRepoTransfer(t *testing.T) {
|
||||
AutoInit: true,
|
||||
}).AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusCreated)
|
||||
DecodeJSON(t, resp, apiRepo)
|
||||
apiRepo := DecodeJSON(t, resp, &api.Repository{})
|
||||
|
||||
// start testing
|
||||
for _, testCase := range testCases {
|
||||
@@ -571,7 +572,6 @@ func transfer(t *testing.T) *repo_model.Repository {
|
||||
session := loginUser(t, user.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
repoName := "moveME"
|
||||
apiRepo := new(api.Repository)
|
||||
req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{
|
||||
Name: repoName,
|
||||
Description: "repo move around",
|
||||
@@ -581,7 +581,7 @@ func transfer(t *testing.T) *repo_model.Repository {
|
||||
}).AddTokenAuth(token)
|
||||
|
||||
resp := MakeRequest(t, req, http.StatusCreated)
|
||||
DecodeJSON(t, resp, apiRepo)
|
||||
apiRepo := DecodeJSON(t, resp, &api.Repository{})
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer", repo.OwnerName, repo.Name), &api.TransferRepoOption{
|
||||
@@ -616,8 +616,7 @@ func TestAPIAcceptTransfer(t *testing.T) {
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/accept", repo.OwnerName, repo.Name)).
|
||||
AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusAccepted)
|
||||
apiRepo := new(api.Repository)
|
||||
DecodeJSON(t, resp, apiRepo)
|
||||
apiRepo := DecodeJSON(t, resp, &api.Repository{})
|
||||
assert.Equal(t, "user4", apiRepo.Owner.UserName)
|
||||
}
|
||||
|
||||
@@ -645,8 +644,7 @@ func TestAPIRejectTransfer(t *testing.T) {
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject", repo.OwnerName, repo.Name)).
|
||||
AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
apiRepo := new(api.Repository)
|
||||
DecodeJSON(t, resp, apiRepo)
|
||||
apiRepo := DecodeJSON(t, resp, &api.Repository{})
|
||||
assert.Equal(t, "user2", apiRepo.Owner.UserName)
|
||||
}
|
||||
|
||||
@@ -659,8 +657,17 @@ 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{
|
||||
Owner: user.Name,
|
||||
Name: "new-repo",
|
||||
@@ -669,9 +676,10 @@ func TestAPIGenerateRepo(t *testing.T) {
|
||||
GitContent: true,
|
||||
}).AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusCreated)
|
||||
DecodeJSON(t, resp, repo)
|
||||
apiRepo := DecodeJSON(t, resp, &api.Repository{})
|
||||
|
||||
assert.Equal(t, "new-repo", repo.Name)
|
||||
assert.Equal(t, "new-repo", apiRepo.Name)
|
||||
assertGeneratedRepoIsUsable(t, user.Name, apiRepo)
|
||||
|
||||
// org
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate", templateRepo.OwnerName, templateRepo.Name), &api.GenerateRepoOption{
|
||||
@@ -682,9 +690,10 @@ func TestAPIGenerateRepo(t *testing.T) {
|
||||
GitContent: true,
|
||||
}).AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusCreated)
|
||||
DecodeJSON(t, resp, repo)
|
||||
apiRepo = DecodeJSON(t, resp, &api.Repository{})
|
||||
|
||||
assert.Equal(t, "new-repo", repo.Name)
|
||||
assert.Equal(t, "new-repo", apiRepo.Name)
|
||||
assertGeneratedRepoIsUsable(t, "org3", apiRepo)
|
||||
}
|
||||
|
||||
func TestAPIRepoGetReviewers(t *testing.T) {
|
||||
|
||||
@@ -154,3 +154,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) {
|
||||
@@ -153,3 +154,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)
|
||||
}
|
||||
|
||||
@@ -92,3 +92,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
|
||||
@@ -200,3 +210,100 @@ func testDeleteAttachmentPermissions(t *testing.T) {
|
||||
// test deleting release attachment from another repo
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestViewBranches(t *testing.T) {
|
||||
@@ -57,9 +58,7 @@ func branchAction(t *testing.T, button string) (*HTMLDoc, string) {
|
||||
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
link, exists := htmlDoc.doc.Find(button).Attr("data-url")
|
||||
if !assert.True(t, exists, "The template has changed") {
|
||||
t.Skip()
|
||||
}
|
||||
require.True(t, exists, "The template has changed")
|
||||
|
||||
req = NewRequest(t, "POST", link)
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/routers"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
@@ -52,16 +53,11 @@ func sessionFileExist(t *testing.T, tmpDir, sessionID string) bool {
|
||||
|
||||
func TestSessionFileCreation(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
oldSessionConfig := setting.SessionConfig.ProviderConfig
|
||||
defer func() {
|
||||
setting.SessionConfig.ProviderConfig = oldSessionConfig
|
||||
testWebRoutes = routers.NormalRoutes()
|
||||
}()
|
||||
defer test.MockVariableValue(&setting.SessionConfig.ProviderConfig)()
|
||||
defer test.MockVariableValue(&testWebRoutes)()
|
||||
|
||||
var config session.Options
|
||||
|
||||
err := json.Unmarshal([]byte(oldSessionConfig), &config)
|
||||
err := json.Unmarshal([]byte(setting.SessionConfig.ProviderConfig), &config)
|
||||
assert.NoError(t, err)
|
||||
|
||||
config.Provider = "file"
|
||||
|
||||
@@ -7,6 +7,7 @@ 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"
|
||||
@@ -14,6 +15,14 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type downloadScopeCase struct {
|
||||
name string
|
||||
method string
|
||||
url string
|
||||
withScope int
|
||||
publicOnlyOK bool
|
||||
}
|
||||
|
||||
func TestDownloadRepoContent(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
@@ -71,3 +80,132 @@ func TestDownloadRepoContent(t *testing.T) {
|
||||
assert.Equal(t, "application/xml", resp.Header().Get("Content-Type"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestDownloadRepoContentTokenScopes(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
ownerReadToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository)
|
||||
miscToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadMisc)
|
||||
publicOnlyToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly)
|
||||
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
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 (
|
||||
base "code.gitea.io/gitea/modules/migration"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/services/migrations"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -28,16 +29,9 @@ import (
|
||||
|
||||
func TestDumpRestore(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
AllowLocalNetworks := setting.Migrations.AllowLocalNetworks
|
||||
setting.Migrations.AllowLocalNetworks = true
|
||||
AppVer := setting.AppVer
|
||||
// Gitea SDK (go-sdk) need to parse the AppVer from server response, so we must set it to a valid version string.
|
||||
setting.AppVer = "1.16.0"
|
||||
defer func() {
|
||||
setting.Migrations.AllowLocalNetworks = AllowLocalNetworks
|
||||
setting.AppVer = AppVer
|
||||
}()
|
||||
|
||||
defer test.MockVariableValue(&setting.AppVer, "1.16.0")()
|
||||
defer test.MockVariableValue(&setting.Migrations.AllowLocalNetworks, true)()
|
||||
assert.NoError(t, migrations.Init())
|
||||
|
||||
reponame := "repo1"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -5,7 +5,6 @@ package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
mathRand "math/rand/v2"
|
||||
@@ -246,8 +245,8 @@ func rawTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS s
|
||||
|
||||
// Request raw paths
|
||||
req := NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", little))
|
||||
resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
|
||||
assert.Equal(t, testFileSizeSmall, resp.Length)
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
assert.Equal(t, testFileSizeSmall, resp.Body.Len())
|
||||
|
||||
if setting.LFS.StartServer {
|
||||
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", littleLFS))
|
||||
@@ -261,8 +260,8 @@ func rawTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS s
|
||||
|
||||
if !testing.Short() {
|
||||
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", big))
|
||||
resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
|
||||
assert.Equal(t, testFileSizeLarge, resp.Length)
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
assert.Equal(t, testFileSizeLarge, resp.Body.Len())
|
||||
|
||||
if setting.LFS.StartServer {
|
||||
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", bigLFS))
|
||||
@@ -287,22 +286,22 @@ func mediaTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS
|
||||
|
||||
// Request media paths
|
||||
req := NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", little))
|
||||
resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
|
||||
assert.Equal(t, testFileSizeSmall, resp.Length)
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
assert.Equal(t, testFileSizeSmall, resp.Body.Len())
|
||||
|
||||
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", littleLFS))
|
||||
resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
|
||||
assert.Equal(t, testFileSizeSmall, resp.Length)
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
assert.Equal(t, testFileSizeSmall, resp.Body.Len())
|
||||
|
||||
if !testing.Short() {
|
||||
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", big))
|
||||
resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
|
||||
assert.Equal(t, testFileSizeLarge, resp.Length)
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
assert.Equal(t, testFileSizeLarge, resp.Body.Len())
|
||||
|
||||
if setting.LFS.StartServer {
|
||||
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", bigLFS))
|
||||
resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
|
||||
assert.Equal(t, testFileSizeLarge, resp.Length)
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
assert.Equal(t, testFileSizeLarge, resp.Body.Len())
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -560,13 +559,11 @@ func doMergeFork(ctx, baseCtx APITestContext, baseBranch, headBranch string) fun
|
||||
t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr))
|
||||
|
||||
// Then get the diff string
|
||||
var diffHash string
|
||||
var diffLength int
|
||||
var diffContent string
|
||||
t.Run("GetDiff", func(t *testing.T) {
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d.diff", url.PathEscape(baseCtx.Username), url.PathEscape(baseCtx.Reponame), pr.Index))
|
||||
resp := ctx.Session.MakeRequestNilResponseHashSumRecorder(t, req, http.StatusOK)
|
||||
diffHash = string(resp.Hash.Sum(nil))
|
||||
diffLength = resp.Length
|
||||
resp := ctx.Session.MakeRequest(t, req, http.StatusOK)
|
||||
diffContent = resp.Body.String()
|
||||
})
|
||||
|
||||
// Now: Merge the PR & make sure that doesn't break the PR page or change its diff
|
||||
@@ -578,17 +575,17 @@ func doMergeFork(ctx, baseCtx APITestContext, baseBranch, headBranch string) fun
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, oldMergeBase, pr2.MergeBase)
|
||||
})
|
||||
t.Run("EnsurDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength))
|
||||
t.Run("EnsurDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffContent))
|
||||
|
||||
// Then: Delete the head branch & make sure that doesn't break the PR page or change its diff
|
||||
t.Run("DeleteHeadBranch", doBranchDelete(baseCtx, baseCtx.Username, baseCtx.Reponame, headBranch))
|
||||
t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr))
|
||||
t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength))
|
||||
t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffContent))
|
||||
|
||||
// Delete the head repository & make sure that doesn't break the PR page or change its diff
|
||||
t.Run("DeleteHeadRepository", doAPIDeleteRepository(ctx))
|
||||
t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr))
|
||||
t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength))
|
||||
t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffContent))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -632,15 +629,12 @@ func doEnsureCanSeePull(ctx APITestContext, pr api.PullRequest) func(t *testing.
|
||||
}
|
||||
}
|
||||
|
||||
func doEnsureDiffNoChange(ctx APITestContext, pr api.PullRequest, diffHash string, diffLength int) func(t *testing.T) {
|
||||
func doEnsureDiffNoChange(ctx APITestContext, pr api.PullRequest, diffContent string) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d.diff", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index))
|
||||
resp := ctx.Session.MakeRequestNilResponseHashSumRecorder(t, req, http.StatusOK)
|
||||
actual := string(resp.Hash.Sum(nil))
|
||||
actualLength := resp.Length
|
||||
|
||||
equal := diffHash == actual
|
||||
assert.True(t, equal, "Unexpected change in the diff string: expected hash: %s size: %d but was actually: %s size: %d", hex.EncodeToString([]byte(diffHash)), diffLength, hex.EncodeToString([]byte(actual)), actualLength)
|
||||
resp := ctx.Session.MakeRequest(t, req, http.StatusOK)
|
||||
actual := resp.Body.String()
|
||||
assert.Equal(t, diffContent, actual)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/common"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
@@ -45,7 +46,7 @@ func TestGitLFSSSH(t *testing.T) {
|
||||
cfg, err := setting.CfgProvider.PrepareSaving()
|
||||
require.NoError(t, err)
|
||||
cfg.Section("server").Key("LFS_ALLOW_PURE_SSH").SetValue("true")
|
||||
setting.LFS.AllowPureSSH = true
|
||||
defer test.MockVariableValue(&setting.LFS.AllowPureSSH, true)()
|
||||
require.NoError(t, cfg.Save())
|
||||
|
||||
_, _, cmdErr := gitcmd.NewCommand("config", "lfs.sshtransfer", "always").
|
||||
|
||||
@@ -63,17 +63,14 @@ func TestDataAsyncDoubleRead_Issue29101(t *testing.T) {
|
||||
|
||||
var data1, data2 []byte
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
wg.Go(func() {
|
||||
data1, _ = io.ReadAll(r1)
|
||||
assert.NoError(t, err)
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
})
|
||||
wg.Go(func() {
|
||||
data2, _ = io.ReadAll(r2)
|
||||
assert.NoError(t, err)
|
||||
wg.Done()
|
||||
}()
|
||||
})
|
||||
wg.Wait()
|
||||
assert.Equal(t, testContent, data1)
|
||||
assert.Equal(t, testContent, data2)
|
||||
|
||||
@@ -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,6 +23,7 @@ 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)
|
||||
})
|
||||
@@ -80,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)()
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -36,12 +37,7 @@ func TestGoGet(t *testing.T) {
|
||||
|
||||
func TestGoGetForSSH(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
old := setting.Repository.GoGetCloneURLProtocol
|
||||
defer func() {
|
||||
setting.Repository.GoGetCloneURLProtocol = old
|
||||
}()
|
||||
setting.Repository.GoGetCloneURLProtocol = "ssh"
|
||||
defer test.MockVariableValue(&setting.Repository.GoGetCloneURLProtocol, "ssh")()
|
||||
|
||||
req := NewRequest(t, "GET", "/blah/glah/plah?go-get=1")
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
@@ -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,
|
||||
"",
|
||||
|
||||
@@ -7,9 +7,8 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"flag"
|
||||
"fmt"
|
||||
"hash"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
@@ -37,49 +36,10 @@ import (
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/xeipuuv/gojsonschema"
|
||||
)
|
||||
|
||||
var testWebRoutes *web.Router
|
||||
|
||||
type NilResponseRecorder struct {
|
||||
httptest.ResponseRecorder
|
||||
Length int
|
||||
}
|
||||
|
||||
func (n *NilResponseRecorder) Write(b []byte) (int, error) {
|
||||
n.Length += len(b)
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// NewRecorder returns an initialized ResponseRecorder.
|
||||
func NewNilResponseRecorder() *NilResponseRecorder {
|
||||
return &NilResponseRecorder{
|
||||
ResponseRecorder: *httptest.NewRecorder(),
|
||||
}
|
||||
}
|
||||
|
||||
type NilResponseHashSumRecorder struct {
|
||||
httptest.ResponseRecorder
|
||||
Hash hash.Hash
|
||||
Length int
|
||||
}
|
||||
|
||||
func (n *NilResponseHashSumRecorder) Write(b []byte) (int, error) {
|
||||
_, _ = n.Hash.Write(b)
|
||||
n.Length += len(b)
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// NewRecorder returns an initialized ResponseRecorder.
|
||||
func NewNilResponseHashSumRecorder() *NilResponseHashSumRecorder {
|
||||
return &NilResponseHashSumRecorder{
|
||||
Hash: fnv.New32(),
|
||||
ResponseRecorder: *httptest.NewRecorder(),
|
||||
}
|
||||
}
|
||||
|
||||
func testMain(m *testing.M) int {
|
||||
defer log.GetManager().Close()
|
||||
|
||||
@@ -116,6 +76,11 @@ func testMain(m *testing.M) int {
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// -test.list must skip InitIntegrationTest, which requires a database.
|
||||
flag.Parse()
|
||||
if flag.Lookup("test.list").Value.String() != "" {
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
os.Exit(testMain(m))
|
||||
}
|
||||
|
||||
@@ -171,42 +136,6 @@ func (s *TestSession) MakeRequest(t testing.TB, rw *RequestWrapper, expectedStat
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *TestSession) MakeRequestNilResponseRecorder(t testing.TB, rw *RequestWrapper, expectedStatus int) *NilResponseRecorder {
|
||||
t.Helper()
|
||||
req := rw.Request
|
||||
baseURL, err := url.Parse(setting.AppURL)
|
||||
assert.NoError(t, err)
|
||||
for _, c := range s.jar.Cookies(baseURL) {
|
||||
req.AddCookie(c)
|
||||
}
|
||||
resp := MakeRequestNilResponseRecorder(t, rw, expectedStatus)
|
||||
|
||||
ch := http.Header{}
|
||||
ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";"))
|
||||
cr := http.Request{Header: ch}
|
||||
s.jar.SetCookies(baseURL, cr.Cookies())
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *TestSession) MakeRequestNilResponseHashSumRecorder(t testing.TB, rw *RequestWrapper, expectedStatus int) *NilResponseHashSumRecorder {
|
||||
t.Helper()
|
||||
req := rw.Request
|
||||
baseURL, err := url.Parse(setting.AppURL)
|
||||
assert.NoError(t, err)
|
||||
for _, c := range s.jar.Cookies(baseURL) {
|
||||
req.AddCookie(c)
|
||||
}
|
||||
resp := MakeRequestNilResponseHashSumRecorder(t, rw, expectedStatus)
|
||||
|
||||
ch := http.Header{}
|
||||
ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";"))
|
||||
cr := http.Request{Header: ch}
|
||||
s.jar.SetCookies(baseURL, cr.Cookies())
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
const userPassword = "password"
|
||||
|
||||
func emptyTestSession(t testing.TB) *TestSession {
|
||||
@@ -262,7 +191,11 @@ func getTokenForLoggedInUser(t testing.TB, session *TestSession, scopes ...auth.
|
||||
req := NewRequestWithURLValues(t, "POST", "/user/settings/applications", urlValues)
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
flashes := session.GetCookieFlashMessage()
|
||||
return flashes.InfoMsg
|
||||
assert.NotNil(t, flashes)
|
||||
if flashes != nil {
|
||||
return flashes.InfoMsg
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type RequestWrapper struct {
|
||||
@@ -330,7 +263,7 @@ func NewRequestWithBody(t testing.TB, method, urlStr string, body io.Reader) *Re
|
||||
t.Fatalf("invalid url str: %s", urlStr)
|
||||
}
|
||||
req, err := http.NewRequest(method, urlStr, body)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
if req.URL.User != nil {
|
||||
req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(req.URL.User.String())))
|
||||
}
|
||||
@@ -358,35 +291,8 @@ func MakeRequest(t testing.TB, rw *RequestWrapper, expectedStatus int) *httptest
|
||||
if expectedStatus != NoExpectedStatus {
|
||||
if expectedStatus != recorder.Code {
|
||||
logUnexpectedResponse(t, recorder)
|
||||
require.Equal(t, expectedStatus, recorder.Code, "Request: %s %s", req.Method, req.URL.String())
|
||||
}
|
||||
}
|
||||
return recorder
|
||||
}
|
||||
|
||||
func MakeRequestNilResponseRecorder(t testing.TB, rw *RequestWrapper, expectedStatus int) *NilResponseRecorder {
|
||||
t.Helper()
|
||||
req := rw.Request
|
||||
recorder := NewNilResponseRecorder()
|
||||
testWebRoutes.ServeHTTP(recorder, req)
|
||||
if expectedStatus != NoExpectedStatus {
|
||||
if !assert.Equal(t, expectedStatus, recorder.Code,
|
||||
"Request: %s %s", req.Method, req.URL.String()) {
|
||||
logUnexpectedResponse(t, &recorder.ResponseRecorder)
|
||||
}
|
||||
}
|
||||
return recorder
|
||||
}
|
||||
|
||||
func MakeRequestNilResponseHashSumRecorder(t testing.TB, rw *RequestWrapper, expectedStatus int) *NilResponseHashSumRecorder {
|
||||
t.Helper()
|
||||
req := rw.Request
|
||||
recorder := NewNilResponseHashSumRecorder()
|
||||
testWebRoutes.ServeHTTP(recorder, req)
|
||||
if expectedStatus != NoExpectedStatus {
|
||||
if !assert.Equal(t, expectedStatus, recorder.Code,
|
||||
"Request: %s %s", req.Method, req.URL.String()) {
|
||||
logUnexpectedResponse(t, &recorder.ResponseRecorder)
|
||||
// don't use "require" which exits the test case and makes "wait group" wait forever
|
||||
assert.Equal(t, expectedStatus, recorder.Code, "Request: %s %s", req.Method, req.URL.String())
|
||||
}
|
||||
}
|
||||
return recorder
|
||||
@@ -422,25 +328,7 @@ func DecodeJSON[T any](t testing.TB, resp *httptest.ResponseRecorder, v T) (ret
|
||||
|
||||
// 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))
|
||||
// don't use "require" which exits the test case and makes "wait group" wait forever
|
||||
assert.NoError(t, decoder.Decode(&v))
|
||||
return v
|
||||
}
|
||||
|
||||
func VerifyJSONSchema(t testing.TB, resp *httptest.ResponseRecorder, schemaFile string) {
|
||||
t.Helper()
|
||||
|
||||
schemaFilePath := filepath.Join(filepath.Dir(setting.AppPath), "tests", "integration", "schemas", schemaFile)
|
||||
_, schemaFileErr := os.Stat(schemaFilePath)
|
||||
assert.NoError(t, schemaFileErr)
|
||||
|
||||
schema, schemaFileReadErr := os.ReadFile(schemaFilePath)
|
||||
assert.NoError(t, schemaFileReadErr)
|
||||
assert.NotEmpty(t, schema)
|
||||
|
||||
nodeinfoSchema := gojsonschema.NewStringLoader(string(schema))
|
||||
nodeinfoString := gojsonschema.NewStringLoader(resp.Body.String())
|
||||
result, schemaValidationErr := gojsonschema.Validate(nodeinfoSchema, nodeinfoString)
|
||||
assert.NoError(t, schemaValidationErr)
|
||||
assert.Empty(t, result.Errors())
|
||||
assert.True(t, result.Valid())
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ import (
|
||||
func getIssuesSelection(t testing.TB, htmlDoc *HTMLDoc) *goquery.Selection {
|
||||
issueList := htmlDoc.doc.Find("#issue-list")
|
||||
assert.Equal(t, 1, issueList.Length())
|
||||
return issueList.Find(".item").Find(".issue-title")
|
||||
return issueList.Find(".item").Find(".issue-item-title")
|
||||
}
|
||||
|
||||
func getIssue(t *testing.T, repoID int64, issueSelection *goquery.Selection) *issues_model.Issue {
|
||||
@@ -692,9 +692,9 @@ func TestIssueReferenceURL(t *testing.T) {
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
|
||||
// the "reference" uses relative URLs, then JS code will convert them to absolute URLs for current origin, in case users are using multiple domains
|
||||
ref, _ := htmlDoc.Find(`.timeline-item.comment.first .reference-issue`).Attr("data-reference")
|
||||
ref, _ := htmlDoc.Find(`.timeline-item.comment.issue-content-comment .reference-issue`).Attr("data-reference")
|
||||
assert.Equal(t, "/user2/repo1/issues/1#issue-1", ref)
|
||||
|
||||
ref, _ = htmlDoc.Find(`.timeline-item.comment:not(.first) .reference-issue`).Attr("data-reference")
|
||||
ref, _ = htmlDoc.Find(`.timeline-item.comment:not(.issue-content-comment) .reference-issue`).Attr("data-reference")
|
||||
assert.Equal(t, "/user2/repo1/issues/1#issuecomment-2", ref)
|
||||
}
|
||||
|
||||
@@ -129,8 +129,7 @@ func TestLFSLockView(t *testing.T) {
|
||||
req.Header.Set("Accept", lfs.AcceptHeader)
|
||||
req.Header.Set("Content-Type", lfs.MediaType)
|
||||
resp := session.MakeRequest(t, req, http.StatusCreated)
|
||||
lockResp := &api.LFSLockResponse{}
|
||||
DecodeJSON(t, resp, lockResp)
|
||||
lockResp := DecodeJSON(t, resp, &api.LFSLockResponse{})
|
||||
lockID = lockResp.Lock.ID
|
||||
}
|
||||
defer func() {
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/services/migrations"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
@@ -36,12 +37,10 @@ import (
|
||||
|
||||
func TestMigrateLocalPath(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
defer test.MockVariableValue(&setting.ImportLocalPaths, true)()
|
||||
|
||||
adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user1"})
|
||||
|
||||
old := setting.ImportLocalPaths
|
||||
setting.ImportLocalPaths = true
|
||||
|
||||
basePath := t.TempDir()
|
||||
|
||||
lowercasePath := filepath.Join(basePath, "lowercase")
|
||||
@@ -57,22 +56,13 @@ func TestMigrateLocalPath(t *testing.T) {
|
||||
|
||||
err = migrations.IsMigrateURLAllowed(mixedcasePath, adminUser)
|
||||
assert.NoError(t, err, "case mixedcase path")
|
||||
|
||||
setting.ImportLocalPaths = old
|
||||
}
|
||||
|
||||
func TestMigrateGiteaForm(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
AllowLocalNetworks := setting.Migrations.AllowLocalNetworks
|
||||
setting.Migrations.AllowLocalNetworks = true
|
||||
AppVer := setting.AppVer
|
||||
// Gitea SDK (go-sdk) need to parse the AppVer from server response, so we must set it to a valid version string.
|
||||
setting.AppVer = "1.16.0"
|
||||
defer func() {
|
||||
setting.Migrations.AllowLocalNetworks = AllowLocalNetworks
|
||||
setting.AppVer = AppVer
|
||||
migrations.Init()
|
||||
}()
|
||||
defer test.MockVariableValue(&setting.Migrations.AllowLocalNetworks, true)()
|
||||
defer test.MockVariableValue(&setting.AppVer, "1.16.0")()
|
||||
assert.NoError(t, migrations.Init())
|
||||
|
||||
ownerName := "user2"
|
||||
@@ -232,14 +222,8 @@ done
|
||||
|
||||
func Test_MigrateFromGiteaToGitea(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
AllowLocalNetworks := setting.Migrations.AllowLocalNetworks
|
||||
setting.Migrations.AllowLocalNetworks = true
|
||||
defer func() {
|
||||
setting.Migrations.AllowLocalNetworks = AllowLocalNetworks
|
||||
migrations.Init()
|
||||
}()
|
||||
require.NoError(t, migrations.Init())
|
||||
defer test.MockVariableValue(&setting.Migrations.AllowLocalNetworks, true)()
|
||||
assert.NoError(t, migrations.Init())
|
||||
|
||||
mockServer := setupGiteaMockServer(t)
|
||||
|
||||
|
||||
@@ -28,10 +28,9 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
var currentEngine *xorm.Engine
|
||||
var currentEngine db.EngineMigration
|
||||
|
||||
func initMigrationTest(t *testing.T) func() {
|
||||
testlogger.Init()
|
||||
@@ -148,7 +147,7 @@ func restoreOldDB(t *testing.T, version string) {
|
||||
_ = sqlDB.Close()
|
||||
}
|
||||
|
||||
func wrappedMigrate(ctx context.Context, x *xorm.Engine) error {
|
||||
func wrappedMigrate(ctx context.Context, x db.EngineMigration) error {
|
||||
currentEngine = x
|
||||
return migrations.Migrate(ctx, x)
|
||||
}
|
||||
@@ -165,7 +164,7 @@ func doMigrationTest(t *testing.T, version string) {
|
||||
|
||||
beans, _ := db.NamesToBean()
|
||||
|
||||
err = db.InitEngineWithMigration(t.Context(), func(ctx context.Context, x *xorm.Engine) error {
|
||||
err = db.InitEngineWithMigration(t.Context(), func(ctx context.Context, x db.EngineMigration) error {
|
||||
currentEngine = x
|
||||
return migrate_base.RecreateTables(beans...)(x)
|
||||
})
|
||||
@@ -173,7 +172,7 @@ func doMigrationTest(t *testing.T, version string) {
|
||||
currentEngine.Close()
|
||||
|
||||
// We do this a second time to ensure that there is not a problem with retained indices
|
||||
err = db.InitEngineWithMigration(t.Context(), func(ctx context.Context, x *xorm.Engine) error {
|
||||
err = db.InitEngineWithMigration(t.Context(), func(ctx context.Context, x db.EngineMigration) error {
|
||||
currentEngine = x
|
||||
return migrate_base.RecreateTables(beans...)(x)
|
||||
})
|
||||
|
||||
@@ -93,6 +93,9 @@ func TestMirrorPull(t *testing.T) {
|
||||
ok := mirror_service.SyncPullMirror(ctx, mirrorRepo.ID)
|
||||
assert.True(t, ok)
|
||||
|
||||
mirror := unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: mirrorRepo.ID})
|
||||
assert.Equal(t, mirror.UpdatedUnix, mirror.LastSyncUnix)
|
||||
|
||||
// actually there is a tag in the source repo, so after "sync", that tag will also come into the mirror
|
||||
initCount++
|
||||
|
||||
@@ -110,4 +113,14 @@ func TestMirrorPull(t *testing.T) {
|
||||
count, err = db.Count[repo_model.Release](t.Context(), findOptions)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, initCount, count)
|
||||
|
||||
mirror = unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: mirrorRepo.ID})
|
||||
lastMirrorSync := mirror.LastSyncUnix
|
||||
assert.NoError(t, mirror_service.UpdateAddress(ctx, mirror, repoPath+"-missing"))
|
||||
|
||||
ok = mirror_service.SyncPullMirror(ctx, mirrorRepo.ID)
|
||||
assert.False(t, ok)
|
||||
|
||||
mirror = unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: mirrorRepo.ID})
|
||||
assert.Equal(t, lastMirrorSync, mirror.LastSyncUnix)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/services/migrations"
|
||||
mirror_service "code.gitea.io/gitea/services/mirror"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
@@ -35,7 +36,7 @@ func TestMirrorPushWikiDefaultBranchMismatch(t *testing.T) {
|
||||
}
|
||||
|
||||
func testMirrorPush(t *testing.T, u *url.URL) {
|
||||
setting.Migrations.AllowLocalNetworks = true
|
||||
defer test.MockVariableValue(&setting.Migrations.AllowLocalNetworks, true)()
|
||||
assert.NoError(t, migrations.Init())
|
||||
|
||||
_ = db.TruncateBeans(t.Context(), &repo_model.PushMirror{})
|
||||
@@ -83,7 +84,7 @@ func testMirrorPush(t *testing.T, u *url.URL) {
|
||||
}
|
||||
|
||||
func testMirrorPushWikiDefaultBranchMismatch(t *testing.T, u *url.URL) {
|
||||
setting.Migrations.AllowLocalNetworks = true
|
||||
defer test.MockVariableValue(&setting.Migrations.AllowLocalNetworks, true)()
|
||||
assert.NoError(t, migrations.Init())
|
||||
|
||||
_ = db.TruncateBeans(t.Context(), &repo_model.PushMirror{})
|
||||
@@ -154,7 +155,7 @@ func doUpdatePushMirror(t *testing.T, session *TestSession, owner, repo string,
|
||||
|
||||
func TestRepoSettingPushMirrorUpdate(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
setting.Migrations.AllowLocalNetworks = true
|
||||
defer test.MockVariableValue(&setting.Migrations.AllowLocalNetworks, true)()
|
||||
assert.NoError(t, migrations.Init())
|
||||
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
// 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"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/web/auth"
|
||||
"code.gitea.io/gitea/services/auth/source/oauth2"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/markbates/goth"
|
||||
"github.com/markbates/goth/gothic"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestOAuth2AvatarFromPicture(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
defer test.MockVariableValue(&setting.OAuth2Client.UpdateAvatar, true)()
|
||||
|
||||
mockServer := createOAuth2MockProvider()
|
||||
defer mockServer.Close()
|
||||
addOAuth2Source(t, "test-oidc-avatar", oauth2.Source{
|
||||
Provider: "openidConnect",
|
||||
ClientID: "test-client-id",
|
||||
OpenIDConnectAutoDiscoveryURL: mockServer.URL + "/.well-known/openid-configuration",
|
||||
})
|
||||
authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), "test-oidc-avatar")
|
||||
require.NoError(t, err)
|
||||
providerName := authSource.Cfg.(*oauth2.Source).Provider
|
||||
|
||||
t.Run("AutoRegister", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.OAuth2Client.Username, "")()
|
||||
defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)()
|
||||
defer test.MockVariableValue(&gothic.CompleteUserAuth, func(res http.ResponseWriter, req *http.Request) (goth.User, error) {
|
||||
return goth.User{
|
||||
Provider: providerName,
|
||||
UserID: "oidc-user-ua-pic",
|
||||
Email: "oidc-user-ua-pic@example.com",
|
||||
Name: "OIDC UA Pic",
|
||||
AvatarURL: mockServer.URL + "/avatar.png",
|
||||
}, nil
|
||||
})()
|
||||
|
||||
req := NewRequest(t, "GET", "/user/oauth2/test-oidc-avatar/callback?code=XYZ&state=XYZ")
|
||||
emptyTestSession(t).MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{LoginName: "oidc-user-ua-pic"})
|
||||
assert.True(t, user.UseCustomAvatar, "avatar must sync (requires Gitea UA)")
|
||||
assert.NotEmpty(t, user.Avatar)
|
||||
})
|
||||
|
||||
t.Run("LinkAccountRegister", func(t *testing.T) {
|
||||
const newUserName = "oidc-link-register"
|
||||
defer web.RouteMockReset()
|
||||
web.RouteMock(web.MockAfterMiddlewares, func(ctx *context.Context) {
|
||||
require.NoError(t, auth.Oauth2SetLinkAccountData(ctx, auth.LinkAccountData{
|
||||
AuthSourceID: authSource.ID,
|
||||
GothUser: goth.User{
|
||||
Provider: providerName,
|
||||
UserID: "oidc-link-register-sub",
|
||||
Email: "oidc-link-register-a@example.com",
|
||||
Name: "OIDC Link Register",
|
||||
AvatarURL: mockServer.URL + "/avatar.png",
|
||||
},
|
||||
}))
|
||||
})
|
||||
|
||||
req := NewRequestWithValues(t, "POST", "/user/link_account_signup", map[string]string{
|
||||
"user_name": newUserName,
|
||||
"email": "oidc-link-register-b@example.com",
|
||||
"password": "AVeryStrongPassword!1",
|
||||
"retype": "AVeryStrongPassword!1",
|
||||
})
|
||||
emptyTestSession(t).MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: newUserName})
|
||||
require.Equal(t, auth_model.OAuth2, user.LoginType)
|
||||
assert.True(t, user.UseCustomAvatar, "register-link flow must sync avatar from `picture` claim")
|
||||
assert.NotEmpty(t, user.Avatar)
|
||||
})
|
||||
}
|
||||
@@ -5,8 +5,11 @@ package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/png"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -56,6 +59,46 @@ func testOAuth2PrepareTestCode(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func createOAuthTestApplication(t *testing.T, userName, name string, redirectURIs []string) *api.OAuth2Application {
|
||||
t.Helper()
|
||||
req := NewRequestWithJSON(t, "POST", "/api/v1/user/applications/oauth2", &api.CreateOAuth2ApplicationOptions{
|
||||
Name: name,
|
||||
RedirectURIs: redirectURIs,
|
||||
ConfidentialClient: true,
|
||||
}).AddBasicAuth(userName)
|
||||
resp := MakeRequest(t, req, http.StatusCreated)
|
||||
created := DecodeJSON(t, resp, &api.OAuth2Application{})
|
||||
require.NotEmpty(t, created.ClientID)
|
||||
require.NotEmpty(t, created.ClientSecret)
|
||||
return created
|
||||
}
|
||||
|
||||
func issueOAuthAuthorizationCode(t *testing.T, user *user_model.User, app *api.OAuth2Application, redirectURI, scope string) (string, string) {
|
||||
t.Helper()
|
||||
|
||||
grant := &auth_model.OAuth2Grant{
|
||||
ApplicationID: app.ID,
|
||||
UserID: user.ID,
|
||||
Scope: scope,
|
||||
}
|
||||
require.NoError(t, db.Insert(t.Context(), grant))
|
||||
|
||||
verifier := "phase3-verifier-" + util.FastCryptoRandomHex(12)
|
||||
challengeBytes := sha256.Sum256([]byte(verifier))
|
||||
code := "phase3-code-" + util.FastCryptoRandomHex(10)
|
||||
|
||||
require.NoError(t, db.Insert(t.Context(), &auth_model.OAuth2AuthorizationCode{
|
||||
GrantID: grant.ID,
|
||||
Code: code,
|
||||
CodeChallenge: base64.RawURLEncoding.EncodeToString(challengeBytes[:]),
|
||||
CodeChallengeMethod: "S256",
|
||||
RedirectURI: redirectURI,
|
||||
ValidUntil: timeutil.TimeStampNow() + 86400,
|
||||
}))
|
||||
|
||||
return code, verifier
|
||||
}
|
||||
|
||||
func TestOAuth2(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
@@ -70,12 +113,14 @@ func TestOAuth2(t *testing.T) {
|
||||
t.Run("AuthorizeRedirectWithExistingGrant", testAuthorizeRedirectWithExistingGrant)
|
||||
t.Run("AuthorizePKCERequiredForPublicClient", testAuthorizePKCERequiredForPublicClient)
|
||||
t.Run("AccessTokenExchange", testAccessTokenExchange)
|
||||
t.Run("AccessTokenExchangeRedirectURIMismatch", testAccessTokenExchangeRedirectURIMismatch)
|
||||
t.Run("AccessTokenExchangeWithPublicClient", testAccessTokenExchangeWithPublicClient)
|
||||
t.Run("AccessTokenExchangeJSON", testAccessTokenExchangeJSON)
|
||||
t.Run("AccessTokenExchangeWithoutPKCE", testAccessTokenExchangeWithoutPKCE)
|
||||
t.Run("AccessTokenExchangeWithInvalidCredentials", testAccessTokenExchangeWithInvalidCredentials)
|
||||
t.Run("AccessTokenExchangeWithBasicAuth", testAccessTokenExchangeWithBasicAuth)
|
||||
t.Run("RefreshTokenInvalidation", testRefreshTokenInvalidation)
|
||||
t.Run("RefreshTokenCrossClientUsage", testRefreshTokenCrossClientUsage)
|
||||
t.Run("OAuthIntrospection", testOAuthIntrospection)
|
||||
t.Run("OAuthGrantScopesReadUserFailRepos", testOAuthGrantScopesReadUserFailRepos)
|
||||
t.Run("OAuthGrantScopesBasicRespectsWriteUser", testOAuthGrantScopesBasicRespectsWriteUser)
|
||||
@@ -223,12 +268,43 @@ func testAccessTokenExchange(t *testing.T) {
|
||||
assert.Greater(t, len(parsed.RefreshToken), 10)
|
||||
}
|
||||
|
||||
func testAccessTokenExchangeRedirectURIMismatch(t *testing.T) {
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
redirectURIs := []string{"https://phase3.example/callback", "https://phase3.example/callback-alt"}
|
||||
app := createOAuthTestApplication(t, user.Name, "phase3-redirect-uri-guard", redirectURIs)
|
||||
code, verifier := issueOAuthAuthorizationCode(t, user, app, redirectURIs[0], "openid profile")
|
||||
|
||||
req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": app.ClientID,
|
||||
"client_secret": app.ClientSecret,
|
||||
"redirect_uri": redirectURIs[1],
|
||||
"code": code,
|
||||
"code_verifier": verifier,
|
||||
})
|
||||
resp := MakeRequest(t, req, http.StatusBadRequest)
|
||||
parsedError := new(oauth2_provider.AccessTokenError)
|
||||
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
|
||||
assert.Equal(t, "invalid_grant", string(parsedError.ErrorCode))
|
||||
assert.Equal(t, "redirect_uri differs from the original authorization request", parsedError.ErrorDescription)
|
||||
|
||||
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": app.ClientID,
|
||||
"client_secret": app.ClientSecret,
|
||||
"redirect_uri": redirectURIs[0],
|
||||
"code": code,
|
||||
"code_verifier": verifier,
|
||||
})
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
}
|
||||
|
||||
func testAccessTokenExchangeWithPublicClient(t *testing.T) {
|
||||
testOAuth2PrepareTestCode(t)
|
||||
req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": "ce5a1322-42a7-11ed-b878-0242ac120002",
|
||||
"redirect_uri": "http://127.0.0.1",
|
||||
"redirect_uri": "http://127.0.0.1/",
|
||||
"code": "authcodepublic",
|
||||
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
||||
})
|
||||
@@ -523,6 +599,54 @@ func testRefreshTokenInvalidation(t *testing.T) {
|
||||
assert.Equal(t, "token was already used", parsedError.ErrorDescription)
|
||||
}
|
||||
|
||||
func testRefreshTokenCrossClientUsage(t *testing.T) {
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
primaryApp := createOAuthTestApplication(t, user.Name, "phase3-refresh-token-primary", []string{"https://phase3.example/refresh-primary"})
|
||||
secondaryApp := createOAuthTestApplication(t, user.Name, "refresh-token-client-guard", []string{"https://alt-client.example/oauth/callback"})
|
||||
code, verifier := issueOAuthAuthorizationCode(t, user, primaryApp, primaryApp.RedirectURIs[0], "openid profile")
|
||||
|
||||
req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": primaryApp.ClientID,
|
||||
"client_secret": primaryApp.ClientSecret,
|
||||
"redirect_uri": primaryApp.RedirectURIs[0],
|
||||
"code": code,
|
||||
"code_verifier": verifier,
|
||||
})
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
type response struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int64 `json:"expires_in"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
parsed := new(response)
|
||||
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed))
|
||||
assert.NotEmpty(t, parsed.RefreshToken)
|
||||
|
||||
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"grant_type": "refresh_token",
|
||||
"client_id": secondaryApp.ClientID,
|
||||
"client_secret": secondaryApp.ClientSecret,
|
||||
"redirect_uri": secondaryApp.RedirectURIs[0],
|
||||
"refresh_token": parsed.RefreshToken,
|
||||
})
|
||||
resp = MakeRequest(t, req, http.StatusBadRequest)
|
||||
parsedError := new(oauth2_provider.AccessTokenError)
|
||||
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
|
||||
assert.Equal(t, "invalid_grant", string(parsedError.ErrorCode))
|
||||
assert.Equal(t, "refresh token belongs to a different client", parsedError.ErrorDescription)
|
||||
|
||||
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"grant_type": "refresh_token",
|
||||
"client_id": primaryApp.ClientID,
|
||||
"client_secret": primaryApp.ClientSecret,
|
||||
"redirect_uri": primaryApp.RedirectURIs[0],
|
||||
"refresh_token": parsed.RefreshToken,
|
||||
})
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
}
|
||||
|
||||
func testOAuthIntrospection(t *testing.T) {
|
||||
testOAuth2PrepareTestCode(t)
|
||||
req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
@@ -1062,6 +1186,13 @@ func createOAuth2MockProvider() *httptest.Server {
|
||||
var mockServer *httptest.Server
|
||||
mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/avatar.png":
|
||||
if !strings.HasPrefix(r.Header.Get("User-Agent"), "Gitea ") {
|
||||
http.Error(w, "user agent doesn't match", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
_ = png.Encode(w, image.NewRGBA(image.Rect(0, 0, 8, 8)))
|
||||
case "/.well-known/openid-configuration":
|
||||
_, _ = w.Write([]byte(`{
|
||||
"issuer": "` + mockServer.URL + `",
|
||||
|
||||
@@ -21,11 +21,6 @@ import (
|
||||
)
|
||||
|
||||
func TestOrgTeamEmailInvite(t *testing.T) {
|
||||
if setting.MailService == nil {
|
||||
t.Skip()
|
||||
return
|
||||
}
|
||||
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
||||
@@ -68,11 +63,6 @@ func TestOrgTeamEmailInvite(t *testing.T) {
|
||||
|
||||
// Check that users are redirected to accept the invitation correctly after login
|
||||
func TestOrgTeamEmailInviteRedirectsExistingUser(t *testing.T) {
|
||||
if setting.MailService == nil {
|
||||
t.Skip()
|
||||
return
|
||||
}
|
||||
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
||||
@@ -139,11 +129,6 @@ func TestOrgTeamEmailInviteRedirectsExistingUser(t *testing.T) {
|
||||
|
||||
// Check that newly signed up users are redirected to accept the invitation correctly
|
||||
func TestOrgTeamEmailInviteRedirectsNewUser(t *testing.T) {
|
||||
if setting.MailService == nil {
|
||||
t.Skip()
|
||||
return
|
||||
}
|
||||
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
||||
@@ -211,11 +196,6 @@ func TestOrgTeamEmailInviteRedirectsNewUser(t *testing.T) {
|
||||
|
||||
// Check that users are redirected correctly after confirming their email
|
||||
func TestOrgTeamEmailInviteRedirectsNewUserWithActivation(t *testing.T) {
|
||||
if setting.MailService == nil {
|
||||
t.Skip()
|
||||
return
|
||||
}
|
||||
|
||||
// enable email confirmation temporarily
|
||||
defer test.MockVariableValue(&setting.Service.RegisterEmailConfirm, true)()
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
@@ -281,11 +261,6 @@ func TestOrgTeamEmailInviteRedirectsNewUserWithActivation(t *testing.T) {
|
||||
// For example: an invite may have been created before the user account was created, but they may be
|
||||
// accepting the invite after having created an account separately
|
||||
func TestOrgTeamEmailInviteRedirectsExistingUserWithLogin(t *testing.T) {
|
||||
if setting.MailService == nil {
|
||||
t.Skip()
|
||||
return
|
||||
}
|
||||
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
pull_model "code.gitea.io/gitea/models/pull"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
@@ -29,6 +30,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/queue"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
@@ -1092,6 +1094,71 @@ func TestPullNonMergeForAdminWithBranchProtection(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestPullForceMergeForBypassAllowlistUser(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
ownerSession := loginUser(t, "user2")
|
||||
ownerCtx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
bypassUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user4"})
|
||||
doAPIAddCollaborator(ownerCtx, bypassUser.Name, perm.AccessModeWrite)(t)
|
||||
|
||||
bypassSession := loginUser(t, bypassUser.Name)
|
||||
forkedName := "repo1-bypass-allowlist"
|
||||
testRepoFork(t, bypassSession, "user2", "repo1", bypassUser.Name, forkedName, "")
|
||||
defer testDeleteRepository(t, bypassSession, bypassUser.Name, forkedName)
|
||||
|
||||
testEditFile(t, bypassSession, bypassUser.Name, forkedName, "master", "README.md", "Hello, World (Bypass Allowlist)\n")
|
||||
resp := testPullCreate(t, bypassSession, bypassUser.Name, forkedName, false, "master", "master", "Bypass allowlist merge test pull")
|
||||
pullURL := test.RedirectURL(resp)
|
||||
elem := strings.Split(pullURL, "/")
|
||||
assert.Equal(t, "pulls", elem[3])
|
||||
|
||||
prIndex, err := strconv.ParseInt(elem[4], 10, 64)
|
||||
assert.NoError(t, err)
|
||||
|
||||
pbCreateReq := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/edit", map[string]string{
|
||||
"rule_name": "master",
|
||||
"enable_push": "all",
|
||||
"enable_status_check": "true",
|
||||
"status_check_contexts": "gitea/actions",
|
||||
"block_admin_merge_override": "true",
|
||||
"enable_bypass_allowlist": "on",
|
||||
"bypass_allowlist_users": strconv.FormatInt(bypassUser.ID, 10),
|
||||
})
|
||||
ownerSession.MakeRequest(t, pbCreateReq, http.StatusSeeOther)
|
||||
defer testAPIDeleteBranchProtection(t, "master", http.StatusNoContent)
|
||||
|
||||
token := getTokenForLoggedInUser(t, bypassSession, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
resp = bypassSession.MakeRequest(t, NewRequest(t, "GET", pullURL), http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
assert.Contains(t, htmlDoc.doc.Find(".merge-section").Text(), "You are allowed to bypass branch protection rules for this merge.")
|
||||
mergeFormProps, exists := htmlDoc.doc.Find("#pull-request-merge-form").Attr("data-merge-form-props")
|
||||
require.True(t, exists)
|
||||
var mergeForm map[string]any
|
||||
require.NoError(t, json.Unmarshal([]byte(mergeFormProps), &mergeForm))
|
||||
assert.Equal(t, true, mergeForm["canMergeNow"])
|
||||
assert.Equal(t, false, mergeForm["allOverridableChecksOk"])
|
||||
|
||||
mergeReq := func(forceMerge bool) *RequestWrapper {
|
||||
return NewRequestWithValues(t, "POST", fmt.Sprintf("/api/v1/repos/user2/repo1/pulls/%d/merge", prIndex), map[string]string{
|
||||
"head_commit_id": "",
|
||||
"merge_when_checks_succeed": "false",
|
||||
"force_merge": strconv.FormatBool(forceMerge),
|
||||
"do": "rebase",
|
||||
}).AddTokenAuth(token)
|
||||
}
|
||||
|
||||
bypassSession.MakeRequest(t, mergeReq(false), http.StatusMethodNotAllowed)
|
||||
bypassSession.MakeRequest(t, mergeReq(true), http.StatusOK)
|
||||
|
||||
baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
|
||||
pr, err := issues_model.GetPullRequestByIndex(t.Context(), baseRepo.ID, prIndex)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, pr.HasMerged)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPullSquashMergeEmpty(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
session := loginUser(t, "user1") // FIXME: don't use admin user for testing
|
||||
|
||||
@@ -140,20 +140,29 @@ func TestPullCreate_EmptyChangesWithDifferentCommits(t *testing.T) {
|
||||
func TestPullCreate_EmptyChangesWithSameCommits(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testCreateBranch(t, session, "user1", "repo1", "branch/master", "status1", http.StatusSeeOther)
|
||||
req := NewRequestWithValues(t, "POST", "/user1/repo1/compare/master...status1",
|
||||
map[string]string{
|
||||
"title": "pull request from status1",
|
||||
},
|
||||
)
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
req = NewRequest(t, "GET", "/user1/repo1/pulls/1")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
|
||||
testCreateBranch(t, session, "user2", "repo1", "branch/master", "empty-pr-branch", http.StatusSeeOther)
|
||||
resp := testPullCreateDirectly(t, session, createPullRequestOptions{
|
||||
BaseRepoOwner: "user2",
|
||||
BaseRepoName: "repo1",
|
||||
BaseBranch: "master",
|
||||
HeadBranch: "empty-pr-branch",
|
||||
Title: "empty pr test",
|
||||
})
|
||||
prURL := test.RedirectURL(resp)
|
||||
|
||||
// check the "merge box" text
|
||||
req := NewRequest(t, "GET", prURL)
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
text := strings.TrimSpace(doc.doc.Find(".merge-section").Text())
|
||||
assert.Contains(t, text, "This branch is already included in the target branch. There is nothing to merge.")
|
||||
|
||||
// check the "files" tab content
|
||||
req = NewRequest(t, "GET", prURL+"/files")
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
doc = NewHTMLParser(t, resp.Body)
|
||||
assert.Equal(t, "Diff Content Not Available", strings.TrimSpace(doc.Find("#diff-container").Text()))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -61,14 +61,34 @@ func TestAPIPullUpdate(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func enableRepoAllowUpdateWithRebase(t *testing.T, repoID int64, allow bool) {
|
||||
func updateRepoPullRequestConfig(t *testing.T, repoID int64, update func(*repo_model.PullRequestsConfig)) {
|
||||
t.Helper()
|
||||
|
||||
repoUnit := unittest.AssertExistsAndLoadBean(t, &repo_model.RepoUnit{RepoID: repoID, Type: unit.TypePullRequests})
|
||||
repoUnit.PullRequestsConfig().AllowRebaseUpdate = allow
|
||||
update(repoUnit.PullRequestsConfig())
|
||||
assert.NoError(t, repo_model.UpdateRepoUnitConfig(t.Context(), repoUnit))
|
||||
}
|
||||
|
||||
func enableRepoAllowUpdateWithRebase(t *testing.T, repoID int64, allow bool) {
|
||||
updateRepoPullRequestConfig(t, repoID, func(c *repo_model.PullRequestsConfig) { c.AllowRebaseUpdate = allow })
|
||||
}
|
||||
|
||||
// setupOutdatedPRWithConfig creates an outdated PR (user2 base, org26 fork), loads its
|
||||
// base repo, head repo, and issue, then applies the supplied PullRequestsConfig mutation.
|
||||
func setupOutdatedPRWithConfig(t *testing.T, mutate func(*repo_model.PullRequestsConfig)) *issues_model.PullRequest {
|
||||
t.Helper()
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
org26 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 26})
|
||||
pr := createOutdatedPR(t, user, org26)
|
||||
require.NoError(t, pr.LoadBaseRepo(t.Context()))
|
||||
require.NoError(t, pr.LoadHeadRepo(t.Context()))
|
||||
require.NoError(t, pr.LoadIssue(t.Context()))
|
||||
if mutate != nil {
|
||||
updateRepoPullRequestConfig(t, pr.BaseRepo.ID, mutate)
|
||||
}
|
||||
return pr
|
||||
}
|
||||
|
||||
func TestAPIPullUpdateByRebase(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
// Create PR to test
|
||||
@@ -120,6 +140,46 @@ func TestAPIPullUpdateByRebase(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestAPIPullUpdateStyleSettings first checks that a disabled explicit style is
|
||||
// forbidden, then guards back-compat: even when the repo's DefaultUpdateStyle is
|
||||
// rebase, an API call with no `style` parameter must still perform a merge
|
||||
// update so existing API clients don't silently flip behavior.
|
||||
func TestAPIPullUpdateStyleSettings(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
pr := setupOutdatedPRWithConfig(t, func(c *repo_model.PullRequestsConfig) {
|
||||
c.AllowMergeUpdate = false
|
||||
c.AllowRebaseUpdate = true
|
||||
c.DefaultUpdateStyle = repo_model.UpdateStyleRebase
|
||||
})
|
||||
|
||||
user40 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40})
|
||||
require.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), pr.BaseRepo, user40, perm.AccessModeWrite))
|
||||
require.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), pr.HeadRepo, user40, perm.AccessModeWrite))
|
||||
|
||||
session := loginUser(t, "user40")
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/pulls/%d/update?style=merge", pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index).
|
||||
AddTokenAuth(token)
|
||||
session.MakeRequest(t, req, http.StatusForbidden)
|
||||
|
||||
updateRepoPullRequestConfig(t, pr.BaseRepo.ID, func(c *repo_model.PullRequestsConfig) {
|
||||
c.AllowMergeUpdate = true
|
||||
})
|
||||
|
||||
req = NewRequestf(t, "POST", "/api/v1/repos/%s/%s/pulls/%d/update", pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index).
|
||||
AddTokenAuth(token)
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// merge update produces a merge commit on top of the head commit (Ahead=2),
|
||||
// rebase update would replay the head commit alone (Ahead=1).
|
||||
diffCount, err := gitrepo.GetDivergingCommits(t.Context(), pr.BaseRepo, pr.BaseBranch, pr.GetGitHeadRefName())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, diffCount.Behind)
|
||||
assert.Equal(t, 2, diffCount.Ahead)
|
||||
})
|
||||
}
|
||||
|
||||
func createOutdatedPR(t *testing.T, actor, forkOrg *user_model.User) *issues_model.PullRequest {
|
||||
baseRepo, err := repo_service.CreateRepository(t.Context(), actor, actor, repo_service.CreateRepoOptions{
|
||||
Name: "repo-pr-update",
|
||||
|
||||
@@ -104,13 +104,7 @@ func TestCreateReleaseDraft(t *testing.T) {
|
||||
|
||||
func TestCreateReleasePaging(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
oldAPIDefaultNum := setting.API.DefaultPagingNum
|
||||
defer func() {
|
||||
setting.API.DefaultPagingNum = oldAPIDefaultNum
|
||||
}()
|
||||
setting.API.DefaultPagingNum = 10
|
||||
|
||||
defer test.MockVariableValue(&setting.API.DefaultPagingNum, 10)()
|
||||
session := loginUser(t, "user2")
|
||||
// Create enough releases to have paging
|
||||
for i := range 12 {
|
||||
|
||||
@@ -180,14 +180,12 @@ func TestRepoCommitsStatusParallel(t *testing.T) {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := range 10 {
|
||||
wg.Add(1)
|
||||
go func(parentT *testing.T, i int) {
|
||||
parentT.Run(fmt.Sprintf("ParallelCreateStatus_%d", i), func(t *testing.T) {
|
||||
wg.Go(func() {
|
||||
t.Run(fmt.Sprintf("ParallelCreateStatus_%d", i), func(t *testing.T) {
|
||||
ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository)
|
||||
doAPICreateCommitStatusTest(ctx, path.Base(commitURL), commitstatus.CommitStatusPending, "testci")(t)
|
||||
wg.Done()
|
||||
})
|
||||
}(t, i)
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
code_indexer "code.gitea.io/gitea/modules/indexer/code"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
@@ -35,8 +36,8 @@ func TestSearchRepo(t *testing.T) {
|
||||
|
||||
testSearch(t, "/user2/repo1/search?q=Description&page=1", []string{"README.md"})
|
||||
|
||||
setting.Indexer.IncludePatterns = setting.IndexerGlobFromString("**.txt")
|
||||
setting.Indexer.ExcludePatterns = setting.IndexerGlobFromString("**/y/**")
|
||||
defer test.MockVariableValue(&setting.Indexer.IncludePatterns, setting.IndexerGlobFromString("**.txt"))()
|
||||
defer test.MockVariableValue(&setting.Indexer.ExcludePatterns, setting.IndexerGlobFromString("**/y/**"))()
|
||||
|
||||
repo, err = repo_model.GetRepositoryByOwnerAndName(t.Context(), "user2", "glob")
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -133,13 +133,11 @@ func testViewRepoWithCache(t *testing.T) {
|
||||
// no last commit cache
|
||||
testView(t)
|
||||
// enable last commit cache for all repositories
|
||||
oldCommitsCount := setting.CacheService.LastCommit.CommitsCount
|
||||
setting.CacheService.LastCommit.CommitsCount = 0
|
||||
defer test.MockVariableValue(&setting.CacheService.LastCommit.CommitsCount, 0)()
|
||||
// first view will not hit the cache
|
||||
testView(t)
|
||||
// second view will hit the cache
|
||||
testView(t)
|
||||
setting.CacheService.LastCommit.CommitsCount = oldCommitsCount
|
||||
}
|
||||
|
||||
func testViewRepoPrivate(t *testing.T) {
|
||||
|
||||
@@ -10,12 +10,13 @@ import (
|
||||
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"
|
||||
)
|
||||
|
||||
func TestRepoWatch(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
// Test round-trip auto-watch
|
||||
setting.Service.AutoWatchOnChanges = true
|
||||
defer test.MockVariableValue(&setting.Service.AutoWatchOnChanges, true)()
|
||||
session := loginUser(t, "user2")
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Watch{UserID: 2, RepoID: 3})
|
||||
testEditFile(t, session, "org3", "repo3", "master", "README.md", "Hello, World (Edited for watch)\n")
|
||||
|
||||
@@ -16,9 +16,7 @@ import (
|
||||
|
||||
func TestSettingShowUserEmailExplore(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
showUserEmail := setting.UI.ShowUserEmail
|
||||
setting.UI.ShowUserEmail = true
|
||||
defer test.MockVariableValue(&setting.UI.ShowUserEmail, true)()
|
||||
|
||||
session := loginUser(t, "user2")
|
||||
req := NewRequest(t, "GET", "/explore/users?sort=alphabetically")
|
||||
@@ -38,8 +36,6 @@ func TestSettingShowUserEmailExplore(t *testing.T) {
|
||||
htmlDoc.doc.Find(".explore.users").Text(),
|
||||
"user34@example.com",
|
||||
)
|
||||
|
||||
setting.UI.ShowUserEmail = showUserEmail
|
||||
}
|
||||
|
||||
func TestSettingShowUserEmailProfile(t *testing.T) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -16,8 +17,7 @@ import (
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
setting.AppVer = "test-version-1"
|
||||
defer test.MockVariableValue(&setting.AppVer, "test-version-1")()
|
||||
req := NewRequest(t, "GET", "/api/v1/version")
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
|
||||
@@ -13,52 +13,17 @@ import (
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func assertFileExist(t *testing.T, p string) {
|
||||
exist, err := util.IsExist(p)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist)
|
||||
}
|
||||
|
||||
func assertFileEqual(t *testing.T, p string, content []byte) {
|
||||
bs, err := os.ReadFile(p)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, content, bs)
|
||||
}
|
||||
|
||||
func TestRepoCloneWiki(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
dstPath := t.TempDir()
|
||||
|
||||
r := u.String() + "user2/repo1.wiki.git"
|
||||
u, _ = url.Parse(r)
|
||||
u.User = url.UserPassword("user2", userPassword)
|
||||
t.Run("Clone", func(t *testing.T) {
|
||||
assert.NoError(t, git.Clone(t.Context(), u.String(), dstPath, git.CloneRepoOptions{}))
|
||||
assertFileEqual(t, filepath.Join(dstPath, "Home.md"), []byte("# Home page\n\nThis is the home page!\n"))
|
||||
assertFileExist(t, filepath.Join(dstPath, "Page-With-Image.md"))
|
||||
assertFileExist(t, filepath.Join(dstPath, "Page-With-Spaced-Name.md"))
|
||||
assertFileExist(t, filepath.Join(dstPath, "images"))
|
||||
assertFileExist(t, filepath.Join(dstPath, "files/Non-Renderable-File.zip"))
|
||||
assertFileExist(t, filepath.Join(dstPath, "jpeg.jpg"))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_RepoWikiPages(t *testing.T) {
|
||||
func TestRepoWikiPages(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
url := "/user2/repo1/wiki/?action=_pages"
|
||||
req := NewRequest(t, "GET", url)
|
||||
req := NewRequest(t, "GET", "/user2/repo1/wiki/?action=_pages")
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
@@ -74,45 +39,72 @@ func Test_RepoWikiPages(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_WikiClone(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
username := "user2"
|
||||
reponame := "repo1"
|
||||
wikiPath := username + "/" + reponame + ".wiki.git"
|
||||
keyname := "my-testing-key"
|
||||
baseAPITestContext := NewAPITestContext(t, username, "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
func testRepoWikiCloneHTTP(t *testing.T, u *url.URL) {
|
||||
// When proc-receive support is enabled globally, the HTTP receive-pack pre-check
|
||||
// must still require write access for wiki repositories. Exercise this with a
|
||||
// normal wiki push because the regression is about the pre-check, not agit refs.
|
||||
require.True(t, git.DefaultFeatures().SupportProcReceive) // modern git should all support proc-receive
|
||||
|
||||
u.Path = wikiPath
|
||||
wikiURL := *u
|
||||
wikiURL.Path = "/user2/repo1.wiki.git"
|
||||
|
||||
t.Run("Clone HTTP", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
dstLocalPath := t.TempDir()
|
||||
|
||||
dstLocalPath := t.TempDir()
|
||||
assert.NoError(t, git.Clone(t.Context(), u.String(), dstLocalPath, git.CloneRepoOptions{}))
|
||||
content, err := os.ReadFile(filepath.Join(dstLocalPath, "Home.md"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "# Home page\n\nThis is the home page!\n", string(content))
|
||||
})
|
||||
// reader can clone
|
||||
wikiURL.User = url.UserPassword("user20", userPassword)
|
||||
require.NoError(t, git.Clone(t.Context(), wikiURL.String(), dstLocalPath, git.CloneRepoOptions{}))
|
||||
_, _, runErr := gitcmd.NewCommand("fast-import").WithDir(dstLocalPath).WithStdinBytes([]byte(`commit refs/heads/master
|
||||
committer unauthorized-user <user20@example.com> 1714310400 +0000
|
||||
data <<EOM
|
||||
dummy-message
|
||||
EOM
|
||||
from refs/heads/master^0
|
||||
M 100644 inline Home.md
|
||||
data <<EOF
|
||||
changed-content
|
||||
EOF
|
||||
`)).RunStdString(t.Context())
|
||||
require.NoError(t, runErr)
|
||||
|
||||
t.Run("Clone SSH", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
content, err := os.ReadFile(filepath.Join(dstLocalPath, "Home.md"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "# Home page\n\nThis is the home page!\n", string(content))
|
||||
|
||||
dstLocalPath := t.TempDir()
|
||||
sshURL := createSSHUrl(wikiPath, u)
|
||||
// reader can't push
|
||||
_, _, runErr = gitcmd.NewCommand("push", "origin", "refs/heads/master").WithDir(dstLocalPath).RunStdString(t.Context())
|
||||
assert.True(t, gitcmd.StderrContains(runErr, "remote: Repository not found\n"))
|
||||
req := NewRequest(t, "GET", "/user2/repo1/wiki/raw/Home.md")
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
assert.Contains(t, resp.Body.String(), "This is the home page!")
|
||||
|
||||
withKeyFile(t, keyname, func(keyFile string) {
|
||||
var keyID int64
|
||||
t.Run("CreateUserKey", doAPICreateUserKey(baseAPITestContext, "test-key", keyFile, func(t *testing.T, key api.PublicKey) {
|
||||
keyID = key.ID
|
||||
}))
|
||||
assert.NotZero(t, keyID)
|
||||
// owner can push
|
||||
wikiURL.User = url.UserPassword("user2", userPassword)
|
||||
_, _, runErr = gitcmd.NewCommand("remote", "add", "origin-owner").AddDynamicArguments(wikiURL.String()).WithDir(dstLocalPath).RunStdString(t.Context())
|
||||
require.NoError(t, runErr)
|
||||
_, _, runErr = gitcmd.NewCommand("push", "origin-owner", "refs/heads/master").WithDir(dstLocalPath).RunStdString(t.Context())
|
||||
assert.NoError(t, runErr)
|
||||
req = NewRequest(t, "GET", "/user2/repo1/wiki/raw/Home.md")
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
assert.Equal(t, "changed-content", strings.TrimSpace(resp.Body.String()))
|
||||
}
|
||||
|
||||
// Setup clone folder
|
||||
assert.NoError(t, git.Clone(t.Context(), sshURL.String(), dstLocalPath, git.CloneRepoOptions{}))
|
||||
content, err := os.ReadFile(filepath.Join(dstLocalPath, "Home.md"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "# Home page\n\nThis is the home page!\n", string(content))
|
||||
})
|
||||
})
|
||||
func testRepoWikiCloneSSH(t *testing.T, u *url.URL) {
|
||||
dstLocalPath := t.TempDir()
|
||||
baseAPITestContext := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
sshURL := createSSHUrl("/user2/repo1.wiki.git", u)
|
||||
|
||||
withKeyFile(t, "my-testing-key", func(keyFile string) {
|
||||
t.Run("CreateUserKey", doAPICreateUserKey(baseAPITestContext, "test-key", keyFile))
|
||||
assert.NoError(t, git.Clone(t.Context(), sshURL.String(), dstLocalPath, git.CloneRepoOptions{}))
|
||||
content, err := os.ReadFile(filepath.Join(dstLocalPath, "Home.md"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "# Home page\n\nThis is the home page!\n", string(content))
|
||||
})
|
||||
}
|
||||
|
||||
func TestRepoWikiClonePush(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
t.Run("SSH", func(t *testing.T) { testRepoWikiCloneSSH(t, u) })
|
||||
t.Run("HTTP", func(t *testing.T) { testRepoWikiCloneHTTP(t, u) })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ func InitIntegrationTest() error {
|
||||
return err
|
||||
}
|
||||
|
||||
setting.Database.SlowQueryThreshold = 0
|
||||
setting.LoadDBSetting()
|
||||
cleanupDb, err := unittest.ResetTestDatabase()
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user