+ {{/* these styles are quite tricky and should also apply to the signup and link_account pages */}}
{{template "user/auth/signin_inner" .}}
diff --git a/templates/user/auth/signin_inner.tmpl b/templates/user/auth/signin_inner.tmpl index fbf86a92bf..de3a1cdea7 100644 --- a/templates/user/auth/signin_inner.tmpl +++ b/templates/user/auth/signin_inner.tmpl @@ -60,10 +60,11 @@
- {{template "user/auth/webauthn_error" .}} -
- + {{if .EnablePasskeyAuth}} + {{template "user/auth/webauthn_error" .}} + + {{end}} {{if .ShowRegistrationButton}}
diff --git a/templates/user/auth/signup_inner.tmpl b/templates/user/auth/signup_inner.tmpl index b3b2a4205e..d66568199d 100644 --- a/templates/user/auth/signup_inner.tmpl +++ b/templates/user/auth/signup_inner.tmpl @@ -59,12 +59,12 @@
diff --git a/templates/user/dashboard/feeds.tmpl b/templates/user/dashboard/feeds.tmpl index 1c1ba5566f..739be586b8 100644 --- a/templates/user/dashboard/feeds.tmpl +++ b/templates/user/dashboard/feeds.tmpl @@ -73,10 +73,13 @@ {{else if .GetOpType.InActions "publish_release"}} {{$linkText := .Content | ctx.RenderUtils.RenderEmoji}} {{ctx.Locale.Tr "action.publish_release" (.GetRepoLink ctx) (printf "%s/releases/tag/%s" (.GetRepoLink ctx) .GetTag) (.ShortRepoPath ctx) $linkText}} - {{else if .GetOpType.InActions "review_dismissed"}} + {{else if .GetOpType.InActions "pull_review_dismissed"}} {{$index := index .GetIssueInfos 0}} {{$reviewer := index .GetIssueInfos 1}} {{ctx.Locale.Tr "action.review_dismissed" (printf "%s/pulls/%s" (.GetRepoLink ctx) $index) $index (.ShortRepoPath ctx) $reviewer}} + {{else if .GetOpType.InActions "auto_merge_pull_request"}} + {{$index := index .GetIssueInfos 0}} + {{ctx.Locale.Tr "action.auto_merge_pull_request" (printf "%s/pulls/%s" (.GetRepoLink ctx) $index) $index (.ShortRepoPath ctx)}} {{end}} {{DateUtils.TimeSince .GetCreate}}
diff --git a/templates/user/dashboard/milestones.tmpl b/templates/user/dashboard/milestones.tmpl index c0059d3cd4..7c1a69a6f5 100644 --- a/templates/user/dashboard/milestones.tmpl +++ b/templates/user/dashboard/milestones.tmpl @@ -100,7 +100,7 @@ {{if .TotalTrackedTime}}
{{svg "octicon-clock"}} - {{.TotalTrackedTime|Sec2Time}} + {{.TotalTrackedTime|Sec2Hour}}
{{end}} {{if .UpdatedUnix}} diff --git a/tests/e2e/utils_e2e.ts b/tests/e2e/utils_e2e.ts index 14ec836600..3e92e0d3c2 100644 --- a/tests/e2e/utils_e2e.ts +++ b/tests/e2e/utils_e2e.ts @@ -1,12 +1,13 @@ import {expect} from '@playwright/test'; import {env} from 'node:process'; +import type {Browser, Page, WorkerInfo} from '@playwright/test'; const ARTIFACTS_PATH = `tests/e2e/test-artifacts`; const LOGIN_PASSWORD = 'password'; // log in user and store session info. This should generally be // run in test.beforeAll(), then the session can be loaded in tests. -export async function login_user(browser, workerInfo, user) { +export async function login_user(browser: Browser, workerInfo: WorkerInfo, user: string) { // Set up a new context const context = await browser.newContext(); const page = await context.newPage(); @@ -17,8 +18,8 @@ export async function login_user(browser, workerInfo, user) { expect(response?.status()).toBe(200); // Status OK // Fill out form - await page.type('input[name=user_name]', user); - await page.type('input[name=password]', LOGIN_PASSWORD); + await page.locator('input[name=user_name]').fill(user); + await page.locator('input[name=password]').fill(LOGIN_PASSWORD); await page.click('form button.ui.primary.button:visible'); await page.waitForLoadState('networkidle'); // eslint-disable-line playwright/no-networkidle @@ -31,7 +32,7 @@ export async function login_user(browser, workerInfo, user) { return context; } -export async function load_logged_in_context(browser, workerInfo, user) { +export async function load_logged_in_context(browser: Browser, workerInfo: WorkerInfo, user: string) { let context; try { context = await browser.newContext({storageState: `${ARTIFACTS_PATH}/state-${user}-${workerInfo.workerIndex}.json`}); @@ -43,7 +44,7 @@ export async function load_logged_in_context(browser, workerInfo, user) { return context; } -export async function save_visual(page) { +export async function save_visual(page: Page) { // Optionally include visual testing if (env.VISUAL_TEST) { await page.waitForLoadState('networkidle'); // eslint-disable-line playwright/no-networkidle diff --git a/tests/gitea-repositories-meta/user2/test_commit_revert.git/HEAD b/tests/gitea-repositories-meta/user2/test_commit_revert.git/HEAD deleted file mode 100644 index b870d82622..0000000000 --- a/tests/gitea-repositories-meta/user2/test_commit_revert.git/HEAD +++ /dev/null @@ -1 +0,0 @@ -ref: refs/heads/main diff --git a/tests/gitea-repositories-meta/user2/test_commit_revert.git/config b/tests/gitea-repositories-meta/user2/test_commit_revert.git/config deleted file mode 100644 index 57bbcba5be..0000000000 --- a/tests/gitea-repositories-meta/user2/test_commit_revert.git/config +++ /dev/null @@ -1,8 +0,0 @@ -[core] - repositoryformatversion = 0 - filemode = true - bare = true - ignorecase = true - precomposeunicode = true -[remote "origin"] - url = https://try.gitea.io/me-heer/test_commit_revert.git diff --git a/tests/gitea-repositories-meta/user2/test_commit_revert.git/objects/pack/pack-91200c8e6707636a6cc3e0d8101fba08b19dcb91.idx b/tests/gitea-repositories-meta/user2/test_commit_revert.git/objects/pack/pack-91200c8e6707636a6cc3e0d8101fba08b19dcb91.idx deleted file mode 100644 index 77bcbe7fb4..0000000000 Binary files a/tests/gitea-repositories-meta/user2/test_commit_revert.git/objects/pack/pack-91200c8e6707636a6cc3e0d8101fba08b19dcb91.idx and /dev/null differ diff --git a/tests/gitea-repositories-meta/user2/test_commit_revert.git/objects/pack/pack-91200c8e6707636a6cc3e0d8101fba08b19dcb91.pack b/tests/gitea-repositories-meta/user2/test_commit_revert.git/objects/pack/pack-91200c8e6707636a6cc3e0d8101fba08b19dcb91.pack deleted file mode 100644 index 7271cdaeb8..0000000000 Binary files a/tests/gitea-repositories-meta/user2/test_commit_revert.git/objects/pack/pack-91200c8e6707636a6cc3e0d8101fba08b19dcb91.pack and /dev/null differ diff --git a/tests/gitea-repositories-meta/user2/test_commit_revert.git/packed-refs b/tests/gitea-repositories-meta/user2/test_commit_revert.git/packed-refs deleted file mode 100644 index 1f546d7fd5..0000000000 --- a/tests/gitea-repositories-meta/user2/test_commit_revert.git/packed-refs +++ /dev/null @@ -1,3 +0,0 @@ -# pack-refs with: peeled fully-peeled sorted -46aa6ab2c881ae90e15d9ccfc947d1625c892ce5 refs/heads/develop -deebcbc752e540bab4ce3ee713d3fc8fdc35b2f7 refs/heads/main diff --git a/tests/gitea-repositories-meta/user2/test_commit_revert.git/refs/heads/main b/tests/gitea-repositories-meta/user2/test_commit_revert.git/refs/heads/main deleted file mode 100644 index ab80ca3ca6..0000000000 --- a/tests/gitea-repositories-meta/user2/test_commit_revert.git/refs/heads/main +++ /dev/null @@ -1 +0,0 @@ -deebcbc752e540bab4ce3ee713d3fc8fdc35b2f7 diff --git a/tests/integration/actions_trigger_test.go b/tests/integration/actions_trigger_test.go index 2c76aa826f..8ea9b34efe 100644 --- a/tests/integration/actions_trigger_test.go +++ b/tests/integration/actions_trigger_test.go @@ -81,12 +81,12 @@ func TestPullRequestTargetEvent(t *testing.T) { OldBranch: "main", NewBranch: "main", Author: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Committer: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Dates: &files_service.CommitDateOptions{ Author: time.Now(), @@ -109,12 +109,12 @@ func TestPullRequestTargetEvent(t *testing.T) { OldBranch: "main", NewBranch: "fork-branch-1", Author: &files_service.IdentityOptions{ - Name: user4.Name, - Email: user4.Email, + GitUserName: user4.Name, + GitUserEmail: user4.Email, }, Committer: &files_service.IdentityOptions{ - Name: user4.Name, - Email: user4.Email, + GitUserName: user4.Name, + GitUserEmail: user4.Email, }, Dates: &files_service.CommitDateOptions{ Author: time.Now(), @@ -164,12 +164,12 @@ func TestPullRequestTargetEvent(t *testing.T) { OldBranch: "main", NewBranch: "fork-branch-2", Author: &files_service.IdentityOptions{ - Name: user4.Name, - Email: user4.Email, + GitUserName: user4.Name, + GitUserEmail: user4.Email, }, Committer: &files_service.IdentityOptions{ - Name: user4.Name, - Email: user4.Email, + GitUserName: user4.Name, + GitUserEmail: user4.Email, }, Dates: &files_service.CommitDateOptions{ Author: time.Now(), @@ -237,12 +237,12 @@ func TestSkipCI(t *testing.T) { OldBranch: "master", NewBranch: "master", Author: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Committer: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Dates: &files_service.CommitDateOptions{ Author: time.Now(), @@ -268,12 +268,12 @@ func TestSkipCI(t *testing.T) { OldBranch: "master", NewBranch: "master", Author: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Committer: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Dates: &files_service.CommitDateOptions{ Author: time.Now(), @@ -299,12 +299,12 @@ func TestSkipCI(t *testing.T) { OldBranch: "master", NewBranch: "test-skip-ci", Author: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Committer: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Dates: &files_service.CommitDateOptions{ Author: time.Now(), @@ -356,12 +356,12 @@ func TestCreateDeleteRefEvent(t *testing.T) { OldBranch: "main", NewBranch: "main", Author: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Committer: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Dates: &files_service.CommitDateOptions{ Author: time.Now(), @@ -470,12 +470,12 @@ func TestPullRequestCommitStatusEvent(t *testing.T) { OldBranch: "main", NewBranch: "main", Author: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Committer: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Dates: &files_service.CommitDateOptions{ Author: time.Now(), @@ -576,12 +576,12 @@ func TestPullRequestCommitStatusEvent(t *testing.T) { OldBranch: testBranch, NewBranch: testBranch, Author: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Committer: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Dates: &files_service.CommitDateOptions{ Author: time.Now(), diff --git a/tests/integration/api_fork_test.go b/tests/integration/api_fork_test.go index 580bb459e7..69f37f4574 100644 --- a/tests/integration/api_fork_test.go +++ b/tests/integration/api_fork_test.go @@ -10,6 +10,7 @@ import ( auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" org_model "code.gitea.io/gitea/models/organization" + 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" @@ -81,8 +82,8 @@ func TestAPIForkListLimitedAndPrivateRepos(t *testing.T) { var forks []*api.Repository DecodeJSON(t, resp, &forks) - assert.Len(t, forks, 1) - assert.EqualValues(t, "1", resp.Header().Get("X-Total-Count")) + assert.Len(t, forks, 2) + assert.EqualValues(t, "2", resp.Header().Get("X-Total-Count")) assert.NoError(t, org_service.AddTeamMember(db.DefaultContext, ownerTeam2, user1)) @@ -96,3 +97,31 @@ func TestAPIForkListLimitedAndPrivateRepos(t *testing.T) { assert.EqualValues(t, "2", resp.Header().Get("X-Total-Count")) }) } + +func TestGetPrivateReposForks(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user1Sess := loginUser(t, "user1") + repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) // private repository + privateOrg := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 23}) + user1Token := getTokenForLoggedInUser(t, user1Sess, auth_model.AccessTokenScopeWriteRepository) + + forkedRepoName := "forked-repo" + // create fork from a private repository + req := NewRequestWithJSON(t, "POST", "/api/v1/repos/"+repo2.FullName()+"/forks", &api.CreateForkOption{ + Organization: &privateOrg.Name, + Name: &forkedRepoName, + }).AddTokenAuth(user1Token) + MakeRequest(t, req, http.StatusAccepted) + + // test get a private fork without clear permissions + req = NewRequest(t, "GET", "/api/v1/repos/"+repo2.FullName()+"/forks").AddTokenAuth(user1Token) + resp := MakeRequest(t, req, http.StatusOK) + + forks := []*api.Repository{} + DecodeJSON(t, resp, &forks) + assert.Len(t, forks, 1) + assert.EqualValues(t, "1", resp.Header().Get("X-Total-Count")) + assert.EqualValues(t, "forked-repo", forks[0].Name) + assert.EqualValues(t, privateOrg.Name, forks[0].Owner.UserName) +} diff --git a/tests/integration/api_org_test.go b/tests/integration/api_org_test.go index fff121490c..d766b1e8be 100644 --- a/tests/integration/api_org_test.go +++ b/tests/integration/api_org_test.go @@ -6,7 +6,6 @@ package integration import ( "fmt" "net/http" - "net/url" "strings" "testing" @@ -19,46 +18,52 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" ) -func TestAPIOrgCreate(t *testing.T) { - onGiteaRun(t, func(*testing.T, *url.URL) { - token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteOrganization) +func TestAPIOrgCreateRename(t *testing.T) { + defer tests.PrepareTestEnv(t)() + token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteOrganization) - org := api.CreateOrgOption{ - UserName: "user1_org", - FullName: "User1's organization", - Description: "This organization created by user1", - Website: "https://try.gitea.io", - Location: "Shanghai", - Visibility: "limited", - } - req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &org). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) + org := api.CreateOrgOption{ + UserName: "user1_org", + FullName: "User1's organization", + Description: "This organization created by user1", + Website: "https://try.gitea.io", + Location: "Shanghai", + Visibility: "limited", + } + req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &org).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) - var apiOrg api.Organization - DecodeJSON(t, resp, &apiOrg) + var apiOrg api.Organization + DecodeJSON(t, resp, &apiOrg) - assert.Equal(t, org.UserName, apiOrg.Name) - assert.Equal(t, org.FullName, apiOrg.FullName) - assert.Equal(t, org.Description, apiOrg.Description) - assert.Equal(t, org.Website, apiOrg.Website) - assert.Equal(t, org.Location, apiOrg.Location) - assert.Equal(t, org.Visibility, apiOrg.Visibility) + assert.Equal(t, org.UserName, apiOrg.Name) + assert.Equal(t, org.FullName, apiOrg.FullName) + assert.Equal(t, org.Description, apiOrg.Description) + assert.Equal(t, org.Website, apiOrg.Website) + assert.Equal(t, org.Location, apiOrg.Location) + assert.Equal(t, org.Visibility, apiOrg.Visibility) - unittest.AssertExistsAndLoadBean(t, &user_model.User{ - Name: org.UserName, - LowerName: strings.ToLower(org.UserName), - FullName: org.FullName, - }) + unittest.AssertExistsAndLoadBean(t, &user_model.User{ + Name: org.UserName, + LowerName: strings.ToLower(org.UserName), + FullName: org.FullName, + }) + // check org name + req = NewRequestf(t, "GET", "/api/v1/orgs/%s", org.UserName).AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiOrg) + assert.EqualValues(t, org.UserName, apiOrg.Name) + + t.Run("CheckPermission", func(t *testing.T) { // Check owner team permission ownerTeam, _ := org_model.GetOwnerTeam(db.DefaultContext, apiOrg.ID) - for _, ut := range unit_model.AllRepoUnitTypes { up := perm.AccessModeOwner if ut == unit_model.TypeExternalTracker || ut == unit_model.TypeExternalWiki { @@ -71,25 +76,10 @@ func TestAPIOrgCreate(t *testing.T) { AccessMode: up, }) } + }) - req = NewRequestf(t, "GET", "/api/v1/orgs/%s", org.UserName). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - DecodeJSON(t, resp, &apiOrg) - assert.EqualValues(t, org.UserName, apiOrg.Name) - - req = NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", org.UserName). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - - var repos []*api.Repository - DecodeJSON(t, resp, &repos) - for _, repo := range repos { - assert.False(t, repo.Private) - } - - req = NewRequestf(t, "GET", "/api/v1/orgs/%s/members", org.UserName). - AddTokenAuth(token) + t.Run("CheckMembers", func(t *testing.T) { + req = NewRequestf(t, "GET", "/api/v1/orgs/%s/members", org.UserName).AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) // user1 on this org is public @@ -98,76 +88,89 @@ func TestAPIOrgCreate(t *testing.T) { assert.Len(t, users, 1) assert.EqualValues(t, "user1", users[0].UserName) }) + + t.Run("RenameOrg", func(t *testing.T) { + req = NewRequestWithJSON(t, "POST", "/api/v1/orgs/user1_org/rename", &api.RenameOrgOption{ + NewName: "renamed_org", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + unittest.AssertExistsAndLoadBean(t, &org_model.Organization{Name: "renamed_org"}) + org.UserName = "renamed_org" // update the variable so the following tests could still use it + }) + + t.Run("ListRepos", func(t *testing.T) { + // FIXME: this test is wrong, there is no repository at all, so the for-loop is empty + req = NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", org.UserName).AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + var repos []*api.Repository + DecodeJSON(t, resp, &repos) + for _, repo := range repos { + assert.False(t, repo.Private) + } + }) } func TestAPIOrgEdit(t *testing.T) { - onGiteaRun(t, func(*testing.T, *url.URL) { - session := loginUser(t, "user1") + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) - org := api.EditOrgOption{ - FullName: "Org3 organization new full name", - Description: "A new description", - Website: "https://try.gitea.io/new", - Location: "Beijing", - Visibility: "private", - } - req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) + org := api.EditOrgOption{ + FullName: "Org3 organization new full name", + Description: "A new description", + Website: "https://try.gitea.io/new", + Location: "Beijing", + Visibility: "private", + } + req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) - var apiOrg api.Organization - DecodeJSON(t, resp, &apiOrg) + var apiOrg api.Organization + DecodeJSON(t, resp, &apiOrg) - assert.Equal(t, "org3", apiOrg.Name) - assert.Equal(t, org.FullName, apiOrg.FullName) - assert.Equal(t, org.Description, apiOrg.Description) - assert.Equal(t, org.Website, apiOrg.Website) - assert.Equal(t, org.Location, apiOrg.Location) - assert.Equal(t, org.Visibility, apiOrg.Visibility) - }) + assert.Equal(t, "org3", apiOrg.Name) + assert.Equal(t, org.FullName, apiOrg.FullName) + assert.Equal(t, org.Description, apiOrg.Description) + assert.Equal(t, org.Website, apiOrg.Website) + assert.Equal(t, org.Location, apiOrg.Location) + assert.Equal(t, org.Visibility, apiOrg.Visibility) } func TestAPIOrgEditBadVisibility(t *testing.T) { - onGiteaRun(t, func(*testing.T, *url.URL) { - session := loginUser(t, "user1") + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) - org := api.EditOrgOption{ - FullName: "Org3 organization new full name", - Description: "A new description", - Website: "https://try.gitea.io/new", - Location: "Beijing", - Visibility: "badvisibility", - } - req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) - }) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) + org := api.EditOrgOption{ + FullName: "Org3 organization new full name", + Description: "A new description", + Website: "https://try.gitea.io/new", + Location: "Beijing", + Visibility: "badvisibility", + } + req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusUnprocessableEntity) } func TestAPIOrgDeny(t *testing.T) { - onGiteaRun(t, func(*testing.T, *url.URL) { - setting.Service.RequireSignInView = true - defer func() { - setting.Service.RequireSignInView = false - }() + defer tests.PrepareTestEnv(t)() + defer test.MockVariableValue(&setting.Service.RequireSignInView, true)() - orgName := "user1_org" - req := NewRequestf(t, "GET", "/api/v1/orgs/%s", orgName) - MakeRequest(t, req, http.StatusNotFound) + orgName := "user1_org" + req := NewRequestf(t, "GET", "/api/v1/orgs/%s", orgName) + MakeRequest(t, req, http.StatusNotFound) - req = NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", orgName) - MakeRequest(t, req, http.StatusNotFound) + req = NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", orgName) + MakeRequest(t, req, http.StatusNotFound) - req = NewRequestf(t, "GET", "/api/v1/orgs/%s/members", orgName) - MakeRequest(t, req, http.StatusNotFound) - }) + req = NewRequestf(t, "GET", "/api/v1/orgs/%s/members", orgName) + MakeRequest(t, req, http.StatusNotFound) } func TestAPIGetAll(t *testing.T) { defer tests.PrepareTestEnv(t)() - token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadOrganization) // accessing with a token will return all orgs @@ -192,37 +195,36 @@ func TestAPIGetAll(t *testing.T) { } func TestAPIOrgSearchEmptyTeam(t *testing.T) { - onGiteaRun(t, func(*testing.T, *url.URL) { - token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteOrganization) - orgName := "org_with_empty_team" + defer tests.PrepareTestEnv(t)() + token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteOrganization) + orgName := "org_with_empty_team" - // create org - req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &api.CreateOrgOption{ - UserName: orgName, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) + // create org + req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &api.CreateOrgOption{ + UserName: orgName, + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusCreated) - // create team with no member - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams", orgName), &api.CreateTeamOption{ - Name: "Empty", - IncludesAllRepositories: true, - Permission: "read", - Units: []string{"repo.code", "repo.issues", "repo.ext_issues", "repo.wiki", "repo.pulls"}, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) + // create team with no member + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams", orgName), &api.CreateTeamOption{ + Name: "Empty", + IncludesAllRepositories: true, + Permission: "read", + Units: []string{"repo.code", "repo.issues", "repo.ext_issues", "repo.wiki", "repo.pulls"}, + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusCreated) - // case-insensitive search for teams that have no members - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/%s/teams/search?q=%s", orgName, "empty")). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - data := struct { - Ok bool - Data []*api.Team - }{} - DecodeJSON(t, resp, &data) - assert.True(t, data.Ok) - if assert.Len(t, data.Data, 1) { - assert.EqualValues(t, "Empty", data.Data[0].Name) - } - }) + // case-insensitive search for teams that have no members + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/%s/teams/search?q=%s", orgName, "empty")). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + data := struct { + Ok bool + Data []*api.Team + }{} + DecodeJSON(t, resp, &data) + assert.True(t, data.Ok) + if assert.Len(t, data.Data, 1) { + assert.EqualValues(t, "Empty", data.Data[0].Name) + } } diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go index 13dc90f8a7..22f26d87d4 100644 --- a/tests/integration/api_repo_test.go +++ b/tests/integration/api_repo_test.go @@ -471,6 +471,15 @@ func TestAPIMirrorSyncNonMirrorRepo(t *testing.T) { assert.Equal(t, "Repository is not a mirror", errRespJSON["message"]) } +func testAPIOrgCreateRepo(t *testing.T, session *TestSession, orgName, repoName string, status int) { + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization, auth_model.AccessTokenScopeWriteRepository) + + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/org/%s/repos", orgName), &api.CreateRepoOption{ + Name: repoName, + }).AddTokenAuth(token) + MakeRequest(t, req, status) +} + func TestAPIOrgRepoCreate(t *testing.T) { testCases := []struct { ctxUserID int64 @@ -488,11 +497,7 @@ func TestAPIOrgRepoCreate(t *testing.T) { for _, testCase := range testCases { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: testCase.ctxUserID}) session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization, auth_model.AccessTokenScopeWriteRepository) - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/org/%s/repos", testCase.orgName), &api.CreateRepoOption{ - Name: testCase.repoName, - }).AddTokenAuth(token) - MakeRequest(t, req, testCase.expectedStatus) + testAPIOrgCreateRepo(t, session, testCase.orgName, testCase.repoName, testCase.expectedStatus) } } diff --git a/tests/integration/api_user_star_test.go b/tests/integration/api_user_star_test.go index 0062889a92..368756528a 100644 --- a/tests/integration/api_user_star_test.go +++ b/tests/integration/api_user_star_test.go @@ -11,7 +11,9 @@ import ( 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" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" @@ -91,3 +93,65 @@ func TestAPIStar(t *testing.T) { MakeRequest(t, req, http.StatusNoContent) }) } + +func TestAPIStarDisabled(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user := "user1" + repo := "user2/repo1" + + session := loginUser(t, user) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) + tokenWithUserScope := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository) + + defer test.MockVariableValue(&setting.Repository.DisableStars, true)() + + t.Run("Star", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "PUT", fmt.Sprintf("/api/v1/user/starred/%s", repo)). + AddTokenAuth(tokenWithUserScope) + MakeRequest(t, req, http.StatusForbidden) + + user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) + req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/user/starred/%s", repo)). + AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteRepository)) + MakeRequest(t, req, http.StatusForbidden) + }) + + t.Run("GetStarredRepos", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s/starred", user)). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + }) + + t.Run("GetMyStarredRepos", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/api/v1/user/starred"). + AddTokenAuth(tokenWithUserScope) + MakeRequest(t, req, http.StatusForbidden) + }) + + t.Run("IsStarring", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/starred/%s", repo)). + AddTokenAuth(tokenWithUserScope) + MakeRequest(t, req, http.StatusForbidden) + + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/starred/%s", repo+"notexisting")). + AddTokenAuth(tokenWithUserScope) + MakeRequest(t, req, http.StatusForbidden) + }) + + t.Run("Unstar", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/starred/%s", repo)). + AddTokenAuth(tokenWithUserScope) + MakeRequest(t, req, http.StatusForbidden) + }) +} diff --git a/tests/integration/api_wiki_test.go b/tests/integration/api_wiki_test.go index 05d90fc4e3..8e5f67e282 100644 --- a/tests/integration/api_wiki_test.go +++ b/tests/integration/api_wiki_test.go @@ -172,6 +172,19 @@ func TestAPIListWikiPages(t *testing.T) { assert.Equal(t, dummymeta, meta) } +func testAPICreateWikiPage(t *testing.T, session *TestSession, userName, repoName, title string, status int) { + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/new", userName, repoName) + + req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateWikiPageOptions{ + Title: title, + ContentBase64: base64.StdEncoding.EncodeToString([]byte("Wiki page content for API unit tests")), + Message: "", + }).AddTokenAuth(token) + MakeRequest(t, req, status) +} + func TestAPINewWikiPage(t *testing.T) { for _, title := range []string{ "New page", @@ -180,16 +193,7 @@ func TestAPINewWikiPage(t *testing.T) { defer tests.PrepareTestEnv(t)() username := "user2" session := loginUser(t, username) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/new", username, "repo1") - - req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateWikiPageOptions{ - Title: title, - ContentBase64: base64.StdEncoding.EncodeToString([]byte("Wiki page content for API unit tests")), - Message: "", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) + testAPICreateWikiPage(t, session, username, "repo1", title, http.StatusCreated) } } diff --git a/tests/integration/benchmarks_test.go b/tests/integration/benchmarks_test.go deleted file mode 100644 index 62da761d2d..0000000000 --- a/tests/integration/benchmarks_test.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2017 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "math/rand/v2" - "net/http" - "net/url" - "testing" - - repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/models/unittest" - api "code.gitea.io/gitea/modules/structs" -) - -// StringWithCharset random string (from https://www.calhoun.io/creating-random-strings-in-go/) -func StringWithCharset(length int, charset string) string { - b := make([]byte, length) - for i := range b { - b[i] = charset[rand.IntN(len(charset))] - } - return string(b) -} - -func BenchmarkRepoBranchCommit(b *testing.B) { - onGiteaRun(b, func(b *testing.B, u *url.URL) { - samples := []int64{1, 2, 3} - b.ResetTimer() - - for _, repoID := range samples { - b.StopTimer() - repo := unittest.AssertExistsAndLoadBean(b, &repo_model.Repository{ID: repoID}) - b.StartTimer() - b.Run(repo.Name, func(b *testing.B) { - session := loginUser(b, "user2") - b.ResetTimer() - b.Run("CreateBranch", func(b *testing.B) { - b.StopTimer() - branchName := StringWithCharset(5+rand.IntN(10), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") - b.StartTimer() - for i := 0; i < b.N; i++ { - b.Run("new_"+branchName, func(b *testing.B) { - b.Skip("benchmark broken") // TODO fix - testAPICreateBranch(b, session, repo.OwnerName, repo.Name, repo.DefaultBranch, "new_"+branchName, http.StatusCreated) - }) - } - }) - b.Run("GetBranches", func(b *testing.B) { - req := NewRequestf(b, "GET", "/api/v1/repos/%s/branches", repo.FullName()) - session.MakeRequest(b, req, http.StatusOK) - }) - b.Run("AccessCommits", func(b *testing.B) { - var branches []*api.Branch - req := NewRequestf(b, "GET", "/api/v1/repos/%s/branches", repo.FullName()) - resp := session.MakeRequest(b, req, http.StatusOK) - DecodeJSON(b, resp, &branches) - b.ResetTimer() // We measure from here - if len(branches) != 0 { - for i := 0; i < b.N; i++ { - req := NewRequestf(b, "GET", "/api/v1/repos/%s/commits?sha=%s", repo.FullName(), branches[i%len(branches)].Name) - session.MakeRequest(b, req, http.StatusOK) - } - } - }) - }) - } - }) -} diff --git a/tests/integration/editor_test.go b/tests/integration/editor_test.go index f0f71b80d1..fa58b8df42 100644 --- a/tests/integration/editor_test.go +++ b/tests/integration/editor_test.go @@ -4,17 +4,26 @@ package integration import ( + "bytes" "fmt" + "io" + "mime/multipart" "net/http" "net/http/httptest" "net/url" "path" "testing" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/json" - gitea_context "code.gitea.io/gitea/services/context" + "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestCreateFile(t *testing.T) { @@ -58,9 +67,8 @@ func TestCreateFileOnProtectedBranch(t *testing.T) { }) session.MakeRequest(t, req, http.StatusSeeOther) // Check if master branch has been locked successfully - flashCookie := session.GetCookie(gitea_context.CookieNameFlash) - assert.NotNil(t, flashCookie) - assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Brule%2B%2522master%2522%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value) + flashMsg := session.GetCookieFlashMessage() + assert.EqualValues(t, `Branch protection for rule "master" has been updated.`, flashMsg.SuccessMsg) // Request editor page req = NewRequest(t, "GET", "/user2/repo1/_new/master/") @@ -98,9 +106,8 @@ func TestCreateFileOnProtectedBranch(t *testing.T) { assert.EqualValues(t, "/user2/repo1/settings/branches", res["redirect"]) // Check if master branch has been locked successfully - flashCookie = session.GetCookie(gitea_context.CookieNameFlash) - assert.NotNil(t, flashCookie) - assert.EqualValues(t, "error%3DRemoving%2Bbranch%2Bprotection%2Brule%2B%25221%2522%2Bfailed.", flashCookie.Value) + flashMsg = session.GetCookieFlashMessage() + assert.EqualValues(t, `Removing branch protection rule "1" failed.`, flashMsg.ErrorMsg) }) } @@ -176,3 +183,156 @@ func TestEditFileToNewBranch(t *testing.T) { testEditFileToNewBranch(t, session, "user2", "repo1", "master", "feature/test", "README.md", "Hello, World (Edited)\n") }) } + +func TestWebGitCommitEmail(t *testing.T) { + onGiteaRun(t, func(t *testing.T, _ *url.URL) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + require.True(t, user.KeepEmailPrivate) + + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + gitRepo, _ := git.OpenRepository(git.DefaultContext, repo1.RepoPath()) + defer gitRepo.Close() + getLastCommit := func(t *testing.T) *git.Commit { + c, err := gitRepo.GetBranchCommit("master") + require.NoError(t, err) + return c + } + + session := loginUser(t, user.Name) + + makeReq := func(t *testing.T, link string, params map[string]string, expectedUserName, expectedEmail string) *httptest.ResponseRecorder { + lastCommit := getLastCommit(t) + params["_csrf"] = GetUserCSRFToken(t, session) + params["last_commit"] = lastCommit.ID.String() + params["commit_choice"] = "direct" + req := NewRequestWithValues(t, "POST", link, params) + resp := session.MakeRequest(t, req, NoExpectedStatus) + newCommit := getLastCommit(t) + if expectedUserName == "" { + require.Equal(t, lastCommit.ID.String(), newCommit.ID.String()) + htmlDoc := NewHTMLParser(t, resp.Body) + errMsg := htmlDoc.doc.Find(".ui.negative.message").Text() + assert.Contains(t, errMsg, translation.NewLocale("en-US").Tr("repo.editor.invalid_commit_email")) + } else { + require.NotEqual(t, lastCommit.ID.String(), newCommit.ID.String()) + assert.EqualValues(t, expectedUserName, newCommit.Author.Name) + assert.EqualValues(t, expectedEmail, newCommit.Author.Email) + assert.EqualValues(t, expectedUserName, newCommit.Committer.Name) + assert.EqualValues(t, expectedEmail, newCommit.Committer.Email) + } + return resp + } + + uploadFile := func(t *testing.T, name, content string) string { + body := &bytes.Buffer{} + uploadForm := multipart.NewWriter(body) + file, _ := uploadForm.CreateFormFile("file", name) + _, _ = io.Copy(file, bytes.NewBufferString(content)) + _ = uploadForm.WriteField("_csrf", GetUserCSRFToken(t, session)) + _ = uploadForm.Close() + + req := NewRequestWithBody(t, "POST", "/user2/repo1/upload-file", body) + req.Header.Add("Content-Type", uploadForm.FormDataContentType()) + resp := session.MakeRequest(t, req, http.StatusOK) + + respMap := map[string]string{} + DecodeJSON(t, resp, &respMap) + return respMap["uuid"] + } + + t.Run("EmailInactive", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + email := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{ID: 35, UID: user.ID}) + require.False(t, email.IsActivated) + makeReq(t, "/user2/repo1/_edit/master/README.md", map[string]string{ + "tree_path": "README.md", + "content": "test content", + "commit_email": email.Email, + }, "", "") + }) + + t.Run("EmailInvalid", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + email := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{ID: 1, IsActivated: true}) + require.NotEqualValues(t, email.UID, user.ID) + makeReq(t, "/user2/repo1/_edit/master/README.md", map[string]string{ + "tree_path": "README.md", + "content": "test content", + "commit_email": email.Email, + }, "", "") + }) + + testWebGit := func(t *testing.T, linkForKeepPrivate string, paramsForKeepPrivate map[string]string, linkForChosenEmail string, paramsForChosenEmail map[string]string) (resp1, resp2 *httptest.ResponseRecorder) { + t.Run("DefaultEmailKeepPrivate", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + paramsForKeepPrivate["commit_email"] = "" + resp1 = makeReq(t, linkForKeepPrivate, paramsForKeepPrivate, "User Two", "user2@noreply.example.org") + }) + t.Run("ChooseEmail", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + paramsForChosenEmail["commit_email"] = "user2@example.com" + resp2 = makeReq(t, linkForChosenEmail, paramsForChosenEmail, "User Two", "user2@example.com") + }) + return resp1, resp2 + } + + t.Run("Edit", func(t *testing.T) { + testWebGit(t, + "/user2/repo1/_edit/master/README.md", map[string]string{"tree_path": "README.md", "content": "for keep private"}, + "/user2/repo1/_edit/master/README.md", map[string]string{"tree_path": "README.md", "content": "for chosen email"}, + ) + }) + + t.Run("UploadDelete", func(t *testing.T) { + file1UUID := uploadFile(t, "file1", "File 1") + file2UUID := uploadFile(t, "file2", "File 2") + testWebGit(t, + "/user2/repo1/_upload/master", map[string]string{"files": file1UUID}, + "/user2/repo1/_upload/master", map[string]string{"files": file2UUID}, + ) + testWebGit(t, + "/user2/repo1/_delete/master/file1", map[string]string{}, + "/user2/repo1/_delete/master/file2", map[string]string{}, + ) + }) + + t.Run("ApplyPatchCherryPick", func(t *testing.T) { + testWebGit(t, + "/user2/repo1/_diffpatch/master", map[string]string{ + "tree_path": "__dummy__", + "content": `diff --git a/patch-file-1.txt b/patch-file-1.txt +new file mode 100644 +index 0000000000..aaaaaaaaaa +--- /dev/null ++++ b/patch-file-1.txt +@@ -0,0 +1 @@ ++File 1 +`, + }, + "/user2/repo1/_diffpatch/master", map[string]string{ + "tree_path": "__dummy__", + "content": `diff --git a/patch-file-2.txt b/patch-file-2.txt +new file mode 100644 +index 0000000000..bbbbbbbbbb +--- /dev/null ++++ b/patch-file-2.txt +@@ -0,0 +1 @@ ++File 2 +`, + }, + ) + + commit1, err := gitRepo.GetCommitByPath("patch-file-1.txt") + require.NoError(t, err) + commit2, err := gitRepo.GetCommitByPath("patch-file-2.txt") + require.NoError(t, err) + resp1, _ := testWebGit(t, + "/user2/repo1/_cherrypick/"+commit1.ID.String()+"/master", map[string]string{"revert": "true"}, + "/user2/repo1/_cherrypick/"+commit2.ID.String()+"/master", map[string]string{"revert": "true"}, + ) + + // By the way, test the "cherrypick" page: a successful revert redirects to the main branch + assert.EqualValues(t, "/user2/repo1/src/branch/master", resp1.Header().Get("Location")) + }) + }) +} diff --git a/tests/integration/feed_repo_test.go b/tests/integration/feed_repo_test.go new file mode 100644 index 0000000000..132ed32ced --- /dev/null +++ b/tests/integration/feed_repo_test.go @@ -0,0 +1,35 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "encoding/xml" + "net/http" + "testing" + + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestFeedRepo(t *testing.T) { + t.Run("RSS", func(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + req := NewRequest(t, "GET", "/user2/repo1.rss") + resp := MakeRequest(t, req, http.StatusOK) + + data := resp.Body.String() + assert.Contains(t, data, `user2/repo1:master
`) - }, 5*time.Second, 100*time.Millisecond) + t.Run("DetectDefaultBranch", func(t *testing.T) { + // the repo shows a prompt to "sync fork" (defaults to the default branch) + require.Eventually(t, func() bool { + resp := session.MakeRequest(t, NewRequestf(t, "GET", "/%s/test-repo-fork/src/branch/fork-branch", forkUser.Name), http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + mergeUpstreamLink = queryMergeUpstreamButtonLink(htmlDoc) + if mergeUpstreamLink == "" { + return false + } + respMsg, _ := htmlDoc.Find(".ui.message:not(.positive)").Html() + return strings.Contains(respMsg, `This branch is 1 commit behind user2/repo1:master`) + }, 5*time.Second, 100*time.Millisecond) + }) + + t.Run("DetectSameBranch", func(t *testing.T) { + // if the fork-branch name also exists in the base repo, then use that branch instead + req = NewRequestWithValues(t, "POST", "/user2/repo1/branches/_new/branch/master", map[string]string{ + "_csrf": GetUserCSRFToken(t, sessionBaseUser), + "new_branch_name": "fork-branch", + }) + sessionBaseUser.MakeRequest(t, req, http.StatusSeeOther) + + require.Eventually(t, func() bool { + resp := session.MakeRequest(t, NewRequestf(t, "GET", "/%s/test-repo-fork/src/branch/fork-branch", forkUser.Name), http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + mergeUpstreamLink = queryMergeUpstreamButtonLink(htmlDoc) + if mergeUpstreamLink == "" { + return false + } + respMsg, _ := htmlDoc.Find(".ui.message:not(.positive)").Html() + return strings.Contains(respMsg, `This branch is 1 commit behind user2/repo1:fork-branch`) + }, 5*time.Second, 100*time.Millisecond) + }) // click the "sync fork" button req = NewRequestWithValues(t, "POST", mergeUpstreamLink, map[string]string{"_csrf": GetUserCSRFToken(t, session)}) session.MakeRequest(t, req, http.StatusOK) checkFileContent("fork-branch", "test-content-1") + + // delete the "fork-branch" from the base repo + req = NewRequestWithValues(t, "POST", "/user2/repo1/branches/delete?name=fork-branch", map[string]string{ + "_csrf": GetUserCSRFToken(t, sessionBaseUser), + }) + sessionBaseUser.MakeRequest(t, req, http.StatusOK) }) t.Run("BaseChangeAfterHeadChange", func(t *testing.T) { diff --git a/tests/integration/repo_mergecommit_revert_test.go b/tests/integration/repo_mergecommit_revert_test.go deleted file mode 100644 index 103fb47e2b..0000000000 --- a/tests/integration/repo_mergecommit_revert_test.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2024 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "net/http" - "testing" - - "code.gitea.io/gitea/tests" - - "github.com/stretchr/testify/assert" -) - -func TestRepoMergeCommitRevert(t *testing.T) { - defer tests.PrepareTestEnv(t)() - session := loginUser(t, "user2") - - req := NewRequest(t, "GET", "/user2/test_commit_revert/_cherrypick/deebcbc752e540bab4ce3ee713d3fc8fdc35b2f7/main?ref=main&refType=branch&cherry-pick-type=revert") - resp := session.MakeRequest(t, req, http.StatusOK) - - htmlDoc := NewHTMLParser(t, resp.Body) - req = NewRequestWithValues(t, "POST", "/user2/test_commit_revert/_cherrypick/deebcbc752e540bab4ce3ee713d3fc8fdc35b2f7/main", map[string]string{ - "_csrf": htmlDoc.GetCSRF(), - "last_commit": "deebcbc752e540bab4ce3ee713d3fc8fdc35b2f7", - "page_has_posted": "true", - "revert": "true", - "commit_summary": "reverting test commit", - "commit_message": "test message", - "commit_choice": "direct", - "new_branch_name": "test-revert-branch-1", - }) - resp = session.MakeRequest(t, req, http.StatusSeeOther) - - // A successful revert redirects to the main branch - assert.EqualValues(t, "/user2/test_commit_revert/src/branch/main", resp.Header().Get("Location")) -} diff --git a/tests/integration/repo_webhook_test.go b/tests/integration/repo_webhook_test.go index ef44a9e2d0..2f9a815fef 100644 --- a/tests/integration/repo_webhook_test.go +++ b/tests/integration/repo_webhook_test.go @@ -4,10 +4,23 @@ package integration import ( + "context" + "fmt" + "io" "net/http" + "net/http/httptest" + "net/url" "strings" "testing" + auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/gitrepo" + "code.gitea.io/gitea/modules/json" + api "code.gitea.io/gitea/modules/structs" + webhook_module "code.gitea.io/gitea/modules/webhook" "code.gitea.io/gitea/tests" "github.com/PuerkitoBio/goquery" @@ -39,3 +52,552 @@ func TestNewWebHookLink(t *testing.T) { }) } } + +func testAPICreateWebhookForRepo(t *testing.T, session *TestSession, userName, repoName, url, event string) { + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) + req := NewRequestWithJSON(t, "POST", "/api/v1/repos/"+userName+"/"+repoName+"/hooks", api.CreateHookOption{ + Type: "gitea", + Config: api.CreateHookOptionConfig{ + "content_type": "json", + "url": url, + }, + Events: []string{event}, + Active: true, + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusCreated) +} + +func testCreateWebhookForRepo(t *testing.T, session *TestSession, webhookType, userName, repoName, url, eventKind string) { + csrf := GetUserCSRFToken(t, session) + req := NewRequestWithValues(t, "POST", "/"+userName+"/"+repoName+"/settings/hooks/"+webhookType+"/new", map[string]string{ + "_csrf": csrf, + "payload_url": url, + "events": eventKind, + "active": "true", + "content_type": fmt.Sprintf("%d", webhook.ContentTypeJSON), + "http_method": "POST", + }) + session.MakeRequest(t, req, http.StatusSeeOther) +} + +func testAPICreateWebhookForOrg(t *testing.T, session *TestSession, userName, url, event string) { + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) + req := NewRequestWithJSON(t, "POST", "/api/v1/orgs/"+userName+"/hooks", api.CreateHookOption{ + Type: "gitea", + Config: api.CreateHookOptionConfig{ + "content_type": "json", + "url": url, + }, + Events: []string{event}, + Active: true, + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusCreated) +} + +type mockWebhookProvider struct { + server *httptest.Server +} + +func newMockWebhookProvider(callback func(r *http.Request), status int) *mockWebhookProvider { + m := &mockWebhookProvider{} + m.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + callback(r) + w.WriteHeader(status) + })) + return m +} + +func (m *mockWebhookProvider) URL() string { + if m.server == nil { + return "" + } + return m.server.URL +} + +// Close closes the mock webhook http server +func (m *mockWebhookProvider) Close() { + if m.server != nil { + m.server.Close() + m.server = nil + } +} + +func Test_WebhookCreate(t *testing.T) { + var payloads []api.CreatePayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.CreatePayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = string(webhook_module.HookEventCreate) + }, http.StatusOK) + defer provider.Close() + + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + // 1. create a new webhook with special webhook for repo1 + session := loginUser(t, "user2") + + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "create") + + // 2. trigger the webhook + testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated) + + // 3. validate the webhook is triggered + assert.Len(t, payloads, 1) + assert.EqualValues(t, string(webhook_module.HookEventCreate), triggeredEvent) + assert.EqualValues(t, "repo1", payloads[0].Repo.Name) + assert.EqualValues(t, "user2/repo1", payloads[0].Repo.FullName) + assert.EqualValues(t, "master2", payloads[0].Ref) + assert.EqualValues(t, "branch", payloads[0].RefType) + }) +} + +func Test_WebhookDelete(t *testing.T) { + var payloads []api.DeletePayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.DeletePayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "delete" + }, http.StatusOK) + defer provider.Close() + + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + // 1. create a new webhook with special webhook for repo1 + session := loginUser(t, "user2") + + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "delete") + + // 2. trigger the webhook + testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated) + testAPIDeleteBranch(t, "master2", http.StatusNoContent) + + // 3. validate the webhook is triggered + assert.EqualValues(t, "delete", triggeredEvent) + assert.Len(t, payloads, 1) + assert.EqualValues(t, "repo1", payloads[0].Repo.Name) + assert.EqualValues(t, "user2/repo1", payloads[0].Repo.FullName) + assert.EqualValues(t, "master2", payloads[0].Ref) + assert.EqualValues(t, "branch", payloads[0].RefType) + }) +} + +func Test_WebhookFork(t *testing.T) { + var payloads []api.ForkPayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.ForkPayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "fork" + }, http.StatusOK) + defer provider.Close() + + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + // 1. create a new webhook with special webhook for repo1 + session := loginUser(t, "user1") + + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "fork") + + // 2. trigger the webhook + testRepoFork(t, session, "user2", "repo1", "user1", "repo1-fork", "master") + + // 3. validate the webhook is triggered + assert.EqualValues(t, "fork", triggeredEvent) + assert.Len(t, payloads, 1) + assert.EqualValues(t, "repo1-fork", payloads[0].Repo.Name) + assert.EqualValues(t, "user1/repo1-fork", payloads[0].Repo.FullName) + assert.EqualValues(t, "repo1", payloads[0].Forkee.Name) + assert.EqualValues(t, "user2/repo1", payloads[0].Forkee.FullName) + }) +} + +func Test_WebhookIssueComment(t *testing.T) { + var payloads []api.IssueCommentPayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.IssueCommentPayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "issue_comment" + }, http.StatusOK) + defer provider.Close() + + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + // 1. create a new webhook with special webhook for repo1 + session := loginUser(t, "user2") + + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issue_comment") + + // 2. trigger the webhook + issueURL := testNewIssue(t, session, "user2", "repo1", "Title2", "Description2") + testIssueAddComment(t, session, issueURL, "issue title2 comment1", "") + + // 3. validate the webhook is triggered + assert.EqualValues(t, "issue_comment", triggeredEvent) + assert.Len(t, payloads, 1) + assert.EqualValues(t, "created", payloads[0].Action) + assert.EqualValues(t, "repo1", payloads[0].Issue.Repo.Name) + assert.EqualValues(t, "user2/repo1", payloads[0].Issue.Repo.FullName) + assert.EqualValues(t, "Title2", payloads[0].Issue.Title) + assert.EqualValues(t, "Description2", payloads[0].Issue.Body) + assert.EqualValues(t, "issue title2 comment1", payloads[0].Comment.Body) + }) +} + +func Test_WebhookRelease(t *testing.T) { + var payloads []api.ReleasePayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.ReleasePayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "release" + }, http.StatusOK) + defer provider.Close() + + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + // 1. create a new webhook with special webhook for repo1 + session := loginUser(t, "user2") + + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "release") + + // 2. trigger the webhook + createNewRelease(t, session, "/user2/repo1", "v0.0.99", "v0.0.99", false, false) + + // 3. validate the webhook is triggered + assert.EqualValues(t, "release", triggeredEvent) + assert.Len(t, payloads, 1) + assert.EqualValues(t, "repo1", payloads[0].Repository.Name) + assert.EqualValues(t, "user2/repo1", payloads[0].Repository.FullName) + assert.EqualValues(t, "v0.0.99", payloads[0].Release.TagName) + assert.False(t, payloads[0].Release.IsDraft) + assert.False(t, payloads[0].Release.IsPrerelease) + }) +} + +func Test_WebhookPush(t *testing.T) { + var payloads []api.PushPayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.PushPayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "push" + }, http.StatusOK) + defer provider.Close() + + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + // 1. create a new webhook with special webhook for repo1 + session := loginUser(t, "user2") + + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "push") + + // 2. trigger the webhook + testCreateFile(t, session, "user2", "repo1", "master", "test_webhook_push.md", "# a test file for webhook push") + + // 3. validate the webhook is triggered + assert.EqualValues(t, "push", triggeredEvent) + assert.Len(t, payloads, 1) + assert.EqualValues(t, "repo1", payloads[0].Repo.Name) + assert.EqualValues(t, "user2/repo1", payloads[0].Repo.FullName) + assert.Len(t, payloads[0].Commits, 1) + assert.EqualValues(t, []string{"test_webhook_push.md"}, payloads[0].Commits[0].Added) + }) +} + +func Test_WebhookIssue(t *testing.T) { + var payloads []api.IssuePayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.IssuePayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "issues" + }, http.StatusOK) + defer provider.Close() + + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + // 1. create a new webhook with special webhook for repo1 + session := loginUser(t, "user2") + + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issues") + + // 2. trigger the webhook + testNewIssue(t, session, "user2", "repo1", "Title1", "Description1") + + // 3. validate the webhook is triggered + assert.EqualValues(t, "issues", triggeredEvent) + assert.Len(t, payloads, 1) + assert.EqualValues(t, "opened", payloads[0].Action) + assert.EqualValues(t, "repo1", payloads[0].Issue.Repo.Name) + assert.EqualValues(t, "user2/repo1", payloads[0].Issue.Repo.FullName) + assert.EqualValues(t, "Title1", payloads[0].Issue.Title) + assert.EqualValues(t, "Description1", payloads[0].Issue.Body) + }) +} + +func Test_WebhookPullRequest(t *testing.T) { + var payloads []api.PullRequestPayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.PullRequestPayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "pull_request" + }, http.StatusOK) + defer provider.Close() + + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + // 1. create a new webhook with special webhook for repo1 + session := loginUser(t, "user2") + + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "pull_request") + + testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated) + // 2. trigger the webhook + repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) + testCreatePullToDefaultBranch(t, session, repo1, repo1, "master2", "first pull request") + + // 3. validate the webhook is triggered + assert.EqualValues(t, "pull_request", triggeredEvent) + assert.Len(t, payloads, 1) + assert.EqualValues(t, "repo1", payloads[0].PullRequest.Base.Repository.Name) + assert.EqualValues(t, "user2/repo1", payloads[0].PullRequest.Base.Repository.FullName) + assert.EqualValues(t, "repo1", payloads[0].PullRequest.Head.Repository.Name) + assert.EqualValues(t, "user2/repo1", payloads[0].PullRequest.Head.Repository.FullName) + assert.EqualValues(t, 0, payloads[0].PullRequest.Additions) + }) +} + +func Test_WebhookPullRequestComment(t *testing.T) { + var payloads []api.IssueCommentPayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.IssueCommentPayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "pull_request_comment" + }, http.StatusOK) + defer provider.Close() + + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + // 1. create a new webhook with special webhook for repo1 + session := loginUser(t, "user2") + + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "pull_request_comment") + + // 2. trigger the webhook + testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated) + repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) + prID := testCreatePullToDefaultBranch(t, session, repo1, repo1, "master2", "first pull request") + + testIssueAddComment(t, session, "/user2/repo1/pulls/"+prID, "pull title2 comment1", "") + + // 3. validate the webhook is triggered + assert.EqualValues(t, "pull_request_comment", triggeredEvent) + assert.Len(t, payloads, 1) + assert.EqualValues(t, "created", payloads[0].Action) + assert.EqualValues(t, "repo1", payloads[0].Issue.Repo.Name) + assert.EqualValues(t, "user2/repo1", payloads[0].Issue.Repo.FullName) + assert.EqualValues(t, "first pull request", payloads[0].Issue.Title) + assert.EqualValues(t, "", payloads[0].Issue.Body) + assert.EqualValues(t, "pull title2 comment1", payloads[0].Comment.Body) + }) +} + +func Test_WebhookWiki(t *testing.T) { + var payloads []api.WikiPayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.WikiPayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "wiki" + }, http.StatusOK) + defer provider.Close() + + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + // 1. create a new webhook with special webhook for repo1 + session := loginUser(t, "user2") + + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "wiki") + + // 2. trigger the webhook + testAPICreateWikiPage(t, session, "user2", "repo1", "Test Wiki Page", http.StatusCreated) + + // 3. validate the webhook is triggered + assert.EqualValues(t, "wiki", triggeredEvent) + assert.Len(t, payloads, 1) + assert.EqualValues(t, "created", payloads[0].Action) + assert.EqualValues(t, "repo1", payloads[0].Repository.Name) + assert.EqualValues(t, "user2/repo1", payloads[0].Repository.FullName) + assert.EqualValues(t, "Test-Wiki-Page", payloads[0].Page) + }) +} + +func Test_WebhookRepository(t *testing.T) { + var payloads []api.RepositoryPayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.RepositoryPayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "repository" + }, http.StatusOK) + defer provider.Close() + + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + // 1. create a new webhook with special webhook for repo1 + session := loginUser(t, "user1") + + testAPICreateWebhookForOrg(t, session, "org3", provider.URL(), "repository") + + // 2. trigger the webhook + testAPIOrgCreateRepo(t, session, "org3", "repo_new", http.StatusCreated) + + // 3. validate the webhook is triggered + assert.EqualValues(t, "repository", triggeredEvent) + assert.Len(t, payloads, 1) + assert.EqualValues(t, "created", payloads[0].Action) + assert.EqualValues(t, "org3", payloads[0].Organization.UserName) + assert.EqualValues(t, "repo_new", payloads[0].Repository.Name) + assert.EqualValues(t, "org3/repo_new", payloads[0].Repository.FullName) + }) +} + +func Test_WebhookPackage(t *testing.T) { + var payloads []api.PackagePayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.PackagePayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "package" + }, http.StatusOK) + defer provider.Close() + + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + // 1. create a new webhook with special webhook for repo1 + session := loginUser(t, "user1") + + testAPICreateWebhookForOrg(t, session, "org3", provider.URL(), "package") + + // 2. trigger the webhook + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) + url := fmt.Sprintf("/api/packages/%s/generic/%s/%s", "org3", "gitea", "v1.24.0") + req := NewRequestWithBody(t, "PUT", url+"/gitea", strings.NewReader("This is a dummy file")). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusCreated) + + // 3. validate the webhook is triggered + assert.EqualValues(t, "package", triggeredEvent) + assert.Len(t, payloads, 1) + assert.EqualValues(t, "created", payloads[0].Action) + assert.EqualValues(t, "gitea", payloads[0].Package.Name) + assert.EqualValues(t, "generic", payloads[0].Package.Type) + assert.EqualValues(t, "org3", payloads[0].Organization.UserName) + assert.EqualValues(t, "v1.24.0", payloads[0].Package.Version) + }) +} + +func Test_WebhookStatus(t *testing.T) { + var payloads []api.CommitStatusPayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + assert.Contains(t, r.Header["X-Github-Event-Type"], "status", "X-GitHub-Event-Type should contain status") + assert.Contains(t, r.Header["X-Gitea-Event-Type"], "status", "X-Gitea-Event-Type should contain status") + assert.Contains(t, r.Header["X-Gogs-Event-Type"], "status", "X-Gogs-Event-Type should contain status") + content, _ := io.ReadAll(r.Body) + var payload api.CommitStatusPayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "status" + }, http.StatusOK) + defer provider.Close() + + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + // 1. create a new webhook with special webhook for repo1 + session := loginUser(t, "user2") + + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "status") + + repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) + + gitRepo1, err := gitrepo.OpenRepository(context.Background(), repo1) + assert.NoError(t, err) + commitID, err := gitRepo1.GetBranchCommitID(repo1.DefaultBranch) + assert.NoError(t, err) + + // 2. trigger the webhook + testCtx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeAll) + + // update a status for a commit via API + doAPICreateCommitStatus(testCtx, commitID, api.CreateStatusOption{ + State: api.CommitStatusSuccess, + TargetURL: "http://test.ci/", + Description: "", + Context: "testci", + })(t) + + // 3. validate the webhook is triggered + assert.EqualValues(t, "status", triggeredEvent) + assert.Len(t, payloads, 1) + assert.EqualValues(t, commitID, payloads[0].Commit.ID) + assert.EqualValues(t, "repo1", payloads[0].Repo.Name) + assert.EqualValues(t, "user2/repo1", payloads[0].Repo.FullName) + assert.EqualValues(t, "testci", payloads[0].Context) + assert.EqualValues(t, commitID, payloads[0].SHA) + }) +} + +func Test_WebhookStatus_NoWrongTrigger(t *testing.T) { + var trigger string + provider := newMockWebhookProvider(func(r *http.Request) { + assert.NotContains(t, r.Header["X-Github-Event-Type"], "status", "X-GitHub-Event-Type should not contain status") + assert.NotContains(t, r.Header["X-Gitea-Event-Type"], "status", "X-Gitea-Event-Type should not contain status") + assert.NotContains(t, r.Header["X-Gogs-Event-Type"], "status", "X-Gogs-Event-Type should not contain status") + trigger = "push" + }, http.StatusOK) + defer provider.Close() + + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + // 1. create a new webhook with special webhook for repo1 + session := loginUser(t, "user2") + + // create a push_only webhook from web UI + testCreateWebhookForRepo(t, session, "gitea", "user2", "repo1", provider.URL(), "push_only") + + // 2. trigger the webhook with a push action + testCreateFile(t, session, "user2", "repo1", "master", "test_webhook_push.md", "# a test file for webhook push") + + // 3. validate the webhook is triggered with right event + assert.EqualValues(t, "push", trigger) + }) +} diff --git a/tests/integration/repofiles_change_test.go b/tests/integration/repofiles_change_test.go index d86dcc01fe..ef520a2e51 100644 --- a/tests/integration/repofiles_change_test.go +++ b/tests/integration/repofiles_change_test.go @@ -71,8 +71,8 @@ func getDeleteRepoFilesOptions(repo *repo_model.Repository) *files_service.Chang NewBranch: repo.DefaultBranch, Message: "Deletes README.md", Author: &files_service.IdentityOptions{ - Name: "Bob Smith", - Email: "bob@smith.com", + GitUserName: "Bob Smith", + GitUserEmail: "bob@smith.com", }, Committer: nil, } diff --git a/tests/integration/signin_test.go b/tests/integration/signin_test.go index d7c0b1bcd3..6738d998d7 100644 --- a/tests/integration/signin_test.go +++ b/tests/integration/signin_test.go @@ -15,8 +15,11 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/tests" + "github.com/markbates/goth" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -76,7 +79,7 @@ func TestSigninWithRememberMe(t *testing.T) { }) session.MakeRequest(t, req, http.StatusSeeOther) - c := session.GetCookie(setting.CookieRememberName) + c := session.GetRawCookie(setting.CookieRememberName) assert.NotNil(t, c) session = emptyTestSession(t) @@ -95,9 +98,14 @@ func TestSigninWithRememberMe(t *testing.T) { session.MakeRequest(t, req, http.StatusOK) } -func TestEnablePasswordSignInForm(t *testing.T) { +func TestEnablePasswordSignInFormAndEnablePasskeyAuth(t *testing.T) { defer tests.PrepareTestEnv(t)() + mockLinkAccount := func(ctx *context.Context) { + gothUser := goth.User{Email: "invalid-email", Name: "."} + _ = ctx.Session.Set("linkAccountGothUser", gothUser) + } + t.Run("EnablePasswordSignInForm=false", func(t *testing.T) { defer tests.PrintCurrentTest(t)() defer test.MockVariableValue(&setting.Service.EnablePasswordSignInForm, false)() @@ -108,6 +116,12 @@ func TestEnablePasswordSignInForm(t *testing.T) { req = NewRequest(t, "POST", "/user/login") MakeRequest(t, req, http.StatusForbidden) + + req = NewRequest(t, "GET", "/user/link_account") + defer web.RouteMockReset() + web.RouteMock(web.MockAfterMiddlewares, mockLinkAccount) + resp = MakeRequest(t, req, http.StatusOK) + NewHTMLParser(t, resp.Body).AssertElement(t, "form[action='/user/link_account_signin']", false) }) t.Run("EnablePasswordSignInForm=true", func(t *testing.T) { @@ -120,5 +134,29 @@ func TestEnablePasswordSignInForm(t *testing.T) { req = NewRequest(t, "POST", "/user/login") MakeRequest(t, req, http.StatusOK) + + req = NewRequest(t, "GET", "/user/link_account") + defer web.RouteMockReset() + web.RouteMock(web.MockAfterMiddlewares, mockLinkAccount) + resp = MakeRequest(t, req, http.StatusOK) + NewHTMLParser(t, resp.Body).AssertElement(t, "form[action='/user/link_account_signin']", true) + }) + + t.Run("EnablePasskeyAuth=false", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer test.MockVariableValue(&setting.Service.EnablePasskeyAuth, false)() + + req := NewRequest(t, "GET", "/user/login") + resp := MakeRequest(t, req, http.StatusOK) + NewHTMLParser(t, resp.Body).AssertElement(t, ".signin-passkey", false) + }) + + t.Run("EnablePasskeyAuth=true", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer test.MockVariableValue(&setting.Service.EnablePasskeyAuth, true)() + + req := NewRequest(t, "GET", "/user/login") + resp := MakeRequest(t, req, http.StatusOK) + NewHTMLParser(t, resp.Body).AssertElement(t, ".signin-passkey", true) }) } diff --git a/tests/integration/user_test.go b/tests/integration/user_test.go index 5b6f28d1ff..bf248a4dde 100644 --- a/tests/integration/user_test.go +++ b/tests/integration/user_test.go @@ -134,8 +134,7 @@ Note: This user hasn't uploaded any GPG keys. =twTO ------END PGP PUBLIC KEY BLOCK----- -`) +-----END PGP PUBLIC KEY BLOCK-----`) // Import key // User1 session := loginUser(t, "user1") @@ -169,8 +168,7 @@ C0TLXKur6NVYQMn01iyL+FZzRpEWNuYF3f9QeeLJ/+l2DafESNhNTy17+RPmacK6 7XhJ1v6JYuh8kaYaEz8OpZDeh7f6Ho6PzJrsy/TKTKhGgZNINj1iaPFyOkQgKR5M GrE0MHOxUbc9tbtyk0F1SuzREUBH =DDXw ------END PGP PUBLIC KEY BLOCK----- -`) +-----END PGP PUBLIC KEY BLOCK-----`) // Export new key testExportUserGPGKeys(t, "user1", `-----BEGIN PGP PUBLIC KEY BLOCK----- @@ -201,8 +199,7 @@ C0TLXKur6NVYQMn01iyL+FZzRpEWNuYF3f9QeeLJ/+l2DafESNhNTy17+RPmacK6 7XhJ1v6JYuh8kaYaEz8OpZDeh7f6Ho6PzJrsy/TKTKhGgZNINj1iaPFyOkQgKR5M GrE0MHOxUbc9tbtyk0F1SuzREUBH =WFf5 ------END PGP PUBLIC KEY BLOCK----- -`) +-----END PGP PUBLIC KEY BLOCK-----`) } func testExportUserGPGKeys(t *testing.T, user, expected string) { diff --git a/tests/mssql.ini.tmpl b/tests/mssql.ini.tmpl index b50816b2cd..ffba516ed3 100644 --- a/tests/mssql.ini.tmpl +++ b/tests/mssql.ini.tmpl @@ -26,6 +26,9 @@ TYPE = immediate [queue.push_update] TYPE = immediate +[queue.webhook_sender] +TYPE = immediate + [repository] ROOT = {{REPO_TEST_DIR}}tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/gitea-repositories @@ -111,3 +114,6 @@ ENABLED = true [actions] ENABLED = true + +[webhook] +ALLOWED_HOST_LIST = 127.0.0.1 diff --git a/tests/mysql.ini.tmpl b/tests/mysql.ini.tmpl index ec8307acc3..e2f2e1390a 100644 --- a/tests/mysql.ini.tmpl +++ b/tests/mysql.ini.tmpl @@ -28,6 +28,9 @@ TYPE = immediate [queue.push_update] TYPE = immediate +[queue.webhook_sender] +TYPE = immediate + [repository] ROOT = {{REPO_TEST_DIR}}tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql/gitea-repositories @@ -118,3 +121,6 @@ REPLY_TO_ADDRESS = incoming+%{token}@localhost [actions] ENABLED = true + +[webhook] +ALLOWED_HOST_LIST = 127.0.0.1 diff --git a/tests/pgsql.ini.tmpl b/tests/pgsql.ini.tmpl index 139ea9c2b7..483b9ed0cd 100644 --- a/tests/pgsql.ini.tmpl +++ b/tests/pgsql.ini.tmpl @@ -27,6 +27,9 @@ TYPE = immediate [queue.push_update] TYPE = immediate +[queue.webhook_sender] +TYPE = immediate + [repository] ROOT = {{REPO_TEST_DIR}}tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-pgsql/gitea-repositories @@ -127,3 +130,6 @@ ENABLED = true [actions] ENABLED = true + +[webhook] +ALLOWED_HOST_LIST = 127.0.0.1 diff --git a/tests/sqlite.ini.tmpl b/tests/sqlite.ini.tmpl index 2f7a3e8182..e837860c26 100644 --- a/tests/sqlite.ini.tmpl +++ b/tests/sqlite.ini.tmpl @@ -22,6 +22,9 @@ TYPE = immediate [queue.push_update] TYPE = immediate +[queue.webhook_sender] +TYPE = immediate + [repository] ROOT = {{REPO_TEST_DIR}}tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/gitea-repositories @@ -116,3 +119,6 @@ RENDER_CONTENT_MODE=sanitized [actions] ENABLED = true + +[webhook] +ALLOWED_HOST_LIST = 127.0.0.1 diff --git a/tsconfig.json b/tsconfig.json index c41f9646d6..ebcb77cd7d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,7 +22,10 @@ "verbatimModuleSyntax": true, "stripInternal": true, "strict": false, + "strictBindCallApply": true, + "strictBuiltinIteratorReturn": true, "strictFunctionTypes": true, + "noImplicitAny": true, "noImplicitThis": true, "noUnusedLocals": true, "noUnusedParameters": true, diff --git a/updates.config.js b/updates.config.js index a4a2fa5228..4ef1ca701b 100644 --- a/updates.config.js +++ b/updates.config.js @@ -3,6 +3,7 @@ export default { '@mcaptcha/vanilla-glue', // breaking changes in rc versions need to be handled 'eslint', // need to migrate to eslint flat config first 'eslint-plugin-array-func', // need to migrate to eslint flat config first + 'eslint-plugin-github', // need to migrate to eslint 9 - https://github.com/github/eslint-plugin-github/issues/585 'eslint-plugin-no-use-extend-native', // need to migrate to eslint flat config first 'eslint-plugin-vitest', // need to migrate to eslint flat config first ], diff --git a/web_src/css/modules/tippy.css b/web_src/css/modules/tippy.css index 55b9751cc6..4438a31c9d 100644 --- a/web_src/css/modules/tippy.css +++ b/web_src/css/modules/tippy.css @@ -28,6 +28,10 @@ z-index: 1; } +.tippy-box[data-theme="default"] { + box-shadow: 0 6px 18px var(--color-shadow); +} + /* bare theme, no styling at all, except box-shadow */ .tippy-box[data-theme="bare"] { border: none; diff --git a/web_src/css/repo.css b/web_src/css/repo.css index e86f81f13c..2752174f86 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -1480,16 +1480,12 @@ td .commit-summary { } .comment-header { - border: none !important; background: var(--color-box-header); - border-bottom: 1px solid var(--color-secondary) !important; - font-weight: var(--font-weight-normal) !important; - padding: 0.5rem 1rem; - margin: 0 !important; + border-bottom: 1px solid var(--color-secondary); + padding: 0 1rem; position: relative; color: var(--color-text); min-height: 41px; - background-color: var(--color-box-header); display: flex; justify-content: space-between; align-items: center; @@ -1634,7 +1630,7 @@ td .commit-summary { } .repo-button-row-left { - flex: 1; + flex-grow: 1; } .repo-button-row .button { diff --git a/web_src/css/repo/clone.css b/web_src/css/repo/clone.css index 3f6a1323fe..c6887fbf16 100644 --- a/web_src/css/repo/clone.css +++ b/web_src/css/repo/clone.css @@ -20,10 +20,12 @@ .clone-panel-tab .item { padding: 5px 10px; background: none; + color: var(--color-text-light-2); } .clone-panel-tab .item.active { - border-bottom: 3px solid var(--color-secondary); + color: var(--color-text-dark); + border-bottom: 3px solid currentcolor; } .clone-panel-tab + .divider { diff --git a/web_src/css/repo/commit-sign.css b/web_src/css/repo/commit-sign.css index 834fdd95d1..56eee62ffc 100644 --- a/web_src/css/repo/commit-sign.css +++ b/web_src/css/repo/commit-sign.css @@ -9,6 +9,7 @@ .ui.label.commit-id-short { font-family: var(--fonts-monospace); + height: 24px; } .ui.label.commit-id-short > .commit-sign-badge { @@ -16,7 +17,7 @@ padding: 0; border: 0 !important; border-radius: 0; - background: transparent; + background: transparent !important; } .ui.label.commit-id-short > .commit-sign-badge:hover { diff --git a/web_src/css/repo/home-file-list.css b/web_src/css/repo/home-file-list.css index 19ba1f2bcb..189b6406d4 100644 --- a/web_src/css/repo/home-file-list.css +++ b/web_src/css/repo/home-file-list.css @@ -65,6 +65,7 @@ } #repo-files-table .repo-file-last-commit { + min-width: 0; /* otherwise the flex axis is not limited and the text might overflow in Pale Moon */ background: var(--color-box-header); } diff --git a/web_src/css/repo/home.css b/web_src/css/repo/home.css index d8ca1339f9..2a3764820b 100644 --- a/web_src/css/repo/home.css +++ b/web_src/css/repo/home.css @@ -1,7 +1,8 @@ .repo-grid-filelist-sidebar { display: grid; - grid-template-columns: auto 300px; + grid-template-columns: auto 280px; grid-template-rows: auto auto 1fr; + gap: var(--page-spacing); } .repo-home-filelist { @@ -13,13 +14,11 @@ .repo-home-sidebar-top { grid-column: 2; grid-row: 1; - padding-left: 1em; } .repo-home-sidebar-bottom { grid-column: 2; grid-row: 2; - padding-left: 1em; } .repo-home-sidebar-bottom .flex-list > :first-child { diff --git a/web_src/js/components/DashboardRepoList.vue b/web_src/js/components/DashboardRepoList.vue index 40ecbba5e3..876292fc94 100644 --- a/web_src/js/components/DashboardRepoList.vue +++ b/web_src/js/components/DashboardRepoList.vue @@ -130,12 +130,12 @@ export default defineComponent({ }, methods: { - changeTab(t) { - this.tab = t; + changeTab(tab: string) { + this.tab = tab; this.updateHistory(); }, - changeReposFilter(filter) { + changeReposFilter(filter: string) { this.reposFilter = filter; this.repos = []; this.page = 1; @@ -218,7 +218,7 @@ export default defineComponent({ this.searchRepos(); }, - changePage(page) { + changePage(page: number) { this.page = page; if (this.page > this.finalPage) { this.page = this.finalPage; @@ -256,7 +256,7 @@ export default defineComponent({ } if (searchedURL === this.searchURL) { - this.repos = json.data.map((webSearchRepo) => { + this.repos = json.data.map((webSearchRepo: any) => { return { ...webSearchRepo.repository, latest_commit_status_state: webSearchRepo.latest_commit_status?.State, // if latest_commit_status is null, it means there is no commit status @@ -264,7 +264,7 @@ export default defineComponent({ locale_latest_commit_status_state: webSearchRepo.locale_latest_commit_status, }; }); - const count = response.headers.get('X-Total-Count'); + const count = Number(response.headers.get('X-Total-Count')); if (searchedQuery === '' && searchedMode === '' && this.archivedFilter === 'both') { this.reposTotalCount = count; } @@ -275,7 +275,7 @@ export default defineComponent({ } }, - repoIcon(repo) { + repoIcon(repo: any) { if (repo.fork) { return 'octicon-repo-forked'; } else if (repo.mirror) { @@ -298,7 +298,7 @@ export default defineComponent({ return commitStatus[status].color; }, - reposFilterKeyControl(e) { + reposFilterKeyControl(e: KeyboardEvent) { switch (e.key) { case 'Enter': document.querySelector('.repo-owner-name-list li.active a')?.click(); diff --git a/web_src/js/components/DiffCommitSelector.vue b/web_src/js/components/DiffCommitSelector.vue index 840acd4b51..16760d1cb1 100644 --- a/web_src/js/components/DiffCommitSelector.vue +++ b/web_src/js/components/DiffCommitSelector.vue @@ -4,6 +4,22 @@ import {SvgIcon} from '../svg.ts'; import {GET} from '../modules/fetch.ts'; import {generateAriaId} from '../modules/fomantic/base.ts'; +type Commit = { + id: string, + hovered: boolean, + selected: boolean, + summary: string, + committer_or_author_name: string, + time: string, + short_sha: string, +} + +type CommitListResult = { + commits: Array, + last_review_commit_sha: string, + locale: Record, +} + export default defineComponent({ components: {SvgIcon}, data: () => { @@ -16,9 +32,9 @@ export default defineComponent({ locale: { filter_changes_by_commit: el.getAttribute('data-filter_changes_by_commit'), } as Record, - commits: [], + commits: [] as Array, hoverActivated: false, - lastReviewCommitSha: null, + lastReviewCommitSha: '', uniqueIdMenu: generateAriaId(), uniqueIdShowAll: generateAriaId(), }; @@ -71,7 +87,7 @@ export default defineComponent({ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { const item = document.activeElement; // try to highlight the selected commits const commitIdx = item?.matches('.item') ? item.getAttribute('data-commit-idx') : null; - if (commitIdx) this.highlight(this.commits[commitIdx]); + if (commitIdx) this.highlight(this.commits[Number(commitIdx)]); } }, onKeyUp(event: KeyboardEvent) { @@ -87,7 +103,7 @@ export default defineComponent({ } } }, - highlight(commit) { + highlight(commit: Commit) { if (!this.hoverActivated) return; const indexSelected = this.commits.findIndex((x) => x.selected); const indexCurrentElem = this.commits.findIndex((x) => x.id === commit.id); @@ -125,10 +141,11 @@ export default defineComponent({ } }); }, + /** Load the commits to show in this dropdown */ async fetchCommits() { const resp = await GET(`${this.issueLink}/commits/list`); - const results = await resp.json(); + const results = await resp.json() as CommitListResult; this.commits.push(...results.commits.map((x) => { x.hovered = false; return x; @@ -166,7 +183,7 @@ export default defineComponent({ * the diff from beginning of PR up to the second clicked commit is * opened */ - commitClickedShift(commit) { + commitClickedShift(commit: Commit) { this.hoverActivated = !this.hoverActivated; commit.selected = true; // Second click -> determine our range and open links accordingly diff --git a/web_src/js/components/DiffFileList.vue b/web_src/js/components/DiffFileList.vue index 792a1aefac..6570c92781 100644 --- a/web_src/js/components/DiffFileList.vue +++ b/web_src/js/components/DiffFileList.vue @@ -18,14 +18,14 @@ function toggleFileList() { } function diffTypeToString(pType: number) { - const diffTypes = { - 1: 'add', - 2: 'modify', - 3: 'del', - 4: 'rename', - 5: 'copy', + const diffTypes: Record = { + '1': 'add', + '2': 'modify', + '3': 'del', + '4': 'rename', + '5': 'copy', }; - return diffTypes[pType]; + return diffTypes[String(pType)]; } function diffStatsWidth(adds: number, dels: number) { diff --git a/web_src/js/components/DiffFileTree.vue b/web_src/js/components/DiffFileTree.vue index 8676c4d37f..d00d03565f 100644 --- a/web_src/js/components/DiffFileTree.vue +++ b/web_src/js/components/DiffFileTree.vue @@ -1,5 +1,5 @@