mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-30 16:11:39 +00:00
1380a46623
Backport #27544 by @earl-warren
- Currently in the cron tasks, the 'Previous Time' only displays the
previous time of when the cron library executes the function, but not
any of the manual executions of the task.
- Store the last run's time in memory in the Task struct and use that,
when that time is later than time that the cron library has executed
this task.
- This ensures that if an instance admin manually starts a task, there's
feedback that this task is/has been run, because the task might be run
that quick, that the status icon already has been changed to an
checkmark,
- Tasks that are executed at startup now reflect this as well, as the
time of the execution of that task on startup is now being shown as
'Previous Time'.
- Added integration tests for the API part, which is easier to test
because querying the HTML table of cron tasks is non-trivial.
- Resolves https://codeberg.org/forgejo/forgejo/issues/949
(cherry picked from commit fd34fdac14
)
Co-authored-by: Earl Warren <109468362+earl-warren@users.noreply.github.com>
Co-authored-by: Gusted <postmaster@gusted.xyz>
Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
Co-authored-by: silverwind <me@silverwind.io>
335 lines
12 KiB
Go
335 lines
12 KiB
Go
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package integration
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
|
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/json"
|
|
api "code.gitea.io/gitea/modules/structs"
|
|
"code.gitea.io/gitea/tests"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestAPIAdminCreateAndDeleteSSHKey(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
// user1 is an admin user
|
|
session := loginUser(t, "user1")
|
|
keyOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"})
|
|
|
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin)
|
|
urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys?token=%s", keyOwner.Name, token)
|
|
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
|
|
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n",
|
|
"title": "test-key",
|
|
})
|
|
resp := MakeRequest(t, req, http.StatusCreated)
|
|
|
|
var newPublicKey api.PublicKey
|
|
DecodeJSON(t, resp, &newPublicKey)
|
|
unittest.AssertExistsAndLoadBean(t, &asymkey_model.PublicKey{
|
|
ID: newPublicKey.ID,
|
|
Name: newPublicKey.Title,
|
|
Fingerprint: newPublicKey.Fingerprint,
|
|
OwnerID: keyOwner.ID,
|
|
})
|
|
|
|
req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d?token=%s",
|
|
keyOwner.Name, newPublicKey.ID, token)
|
|
MakeRequest(t, req, http.StatusNoContent)
|
|
unittest.AssertNotExistsBean(t, &asymkey_model.PublicKey{ID: newPublicKey.ID})
|
|
}
|
|
|
|
func TestAPIAdminDeleteMissingSSHKey(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
// user1 is an admin user
|
|
token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteAdmin)
|
|
req := NewRequestf(t, "DELETE", "/api/v1/admin/users/user1/keys/%d?token=%s", unittest.NonexistentID, token)
|
|
MakeRequest(t, req, http.StatusNotFound)
|
|
}
|
|
|
|
func TestAPIAdminDeleteUnauthorizedKey(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
adminUsername := "user1"
|
|
normalUsername := "user2"
|
|
token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
|
|
|
|
urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys?token=%s", adminUsername, token)
|
|
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
|
|
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n",
|
|
"title": "test-key",
|
|
})
|
|
resp := MakeRequest(t, req, http.StatusCreated)
|
|
var newPublicKey api.PublicKey
|
|
DecodeJSON(t, resp, &newPublicKey)
|
|
|
|
token = getUserToken(t, normalUsername)
|
|
req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d?token=%s",
|
|
adminUsername, newPublicKey.ID, token)
|
|
MakeRequest(t, req, http.StatusForbidden)
|
|
}
|
|
|
|
func TestAPISudoUser(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
adminUsername := "user1"
|
|
normalUsername := "user2"
|
|
token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeReadUser)
|
|
|
|
urlStr := fmt.Sprintf("/api/v1/user?sudo=%s&token=%s", normalUsername, token)
|
|
req := NewRequest(t, "GET", urlStr)
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
var user api.User
|
|
DecodeJSON(t, resp, &user)
|
|
|
|
assert.Equal(t, normalUsername, user.UserName)
|
|
}
|
|
|
|
func TestAPISudoUserForbidden(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
adminUsername := "user1"
|
|
normalUsername := "user2"
|
|
|
|
token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeReadAdmin)
|
|
urlStr := fmt.Sprintf("/api/v1/user?sudo=%s&token=%s", adminUsername, token)
|
|
req := NewRequest(t, "GET", urlStr)
|
|
MakeRequest(t, req, http.StatusForbidden)
|
|
}
|
|
|
|
func TestAPIListUsers(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
adminUsername := "user1"
|
|
token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeReadAdmin)
|
|
|
|
urlStr := fmt.Sprintf("/api/v1/admin/users?token=%s", token)
|
|
req := NewRequest(t, "GET", urlStr)
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
var users []api.User
|
|
DecodeJSON(t, resp, &users)
|
|
|
|
found := false
|
|
for _, user := range users {
|
|
if user.UserName == adminUsername {
|
|
found = true
|
|
}
|
|
}
|
|
assert.True(t, found)
|
|
numberOfUsers := unittest.GetCount(t, &user_model.User{}, "type = 0")
|
|
assert.Len(t, users, numberOfUsers)
|
|
}
|
|
|
|
func TestAPIListUsersNotLoggedIn(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
req := NewRequest(t, "GET", "/api/v1/admin/users")
|
|
MakeRequest(t, req, http.StatusUnauthorized)
|
|
}
|
|
|
|
func TestAPIListUsersNonAdmin(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
nonAdminUsername := "user2"
|
|
token := getUserToken(t, nonAdminUsername)
|
|
req := NewRequestf(t, "GET", "/api/v1/admin/users?token=%s", token)
|
|
MakeRequest(t, req, http.StatusForbidden)
|
|
}
|
|
|
|
func TestAPICreateUserInvalidEmail(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
adminUsername := "user1"
|
|
token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
|
|
urlStr := fmt.Sprintf("/api/v1/admin/users?token=%s", token)
|
|
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
|
|
"email": "invalid_email@domain.com\r\n",
|
|
"full_name": "invalid user",
|
|
"login_name": "invalidUser",
|
|
"must_change_password": "true",
|
|
"password": "password",
|
|
"send_notify": "true",
|
|
"source_id": "0",
|
|
"username": "invalidUser",
|
|
})
|
|
MakeRequest(t, req, http.StatusUnprocessableEntity)
|
|
}
|
|
|
|
func TestAPICreateAndDeleteUser(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
adminUsername := "user1"
|
|
token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
|
|
|
|
req := NewRequestWithValues(
|
|
t,
|
|
"POST",
|
|
fmt.Sprintf("/api/v1/admin/users?token=%s", token),
|
|
map[string]string{
|
|
"email": "deleteme@domain.com",
|
|
"full_name": "delete me",
|
|
"login_name": "deleteme",
|
|
"must_change_password": "true",
|
|
"password": "password",
|
|
"send_notify": "true",
|
|
"source_id": "0",
|
|
"username": "deleteme",
|
|
},
|
|
)
|
|
MakeRequest(t, req, http.StatusCreated)
|
|
|
|
req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/admin/users/deleteme?token=%s", token))
|
|
MakeRequest(t, req, http.StatusNoContent)
|
|
}
|
|
|
|
func TestAPIEditUser(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
adminUsername := "user1"
|
|
token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
|
|
urlStr := fmt.Sprintf("/api/v1/admin/users/%s?token=%s", "user2", token)
|
|
|
|
req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{
|
|
// required
|
|
"login_name": "user2",
|
|
"source_id": "0",
|
|
// to change
|
|
"full_name": "Full Name User 2",
|
|
})
|
|
MakeRequest(t, req, http.StatusOK)
|
|
|
|
empty := ""
|
|
req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{
|
|
LoginName: "user2",
|
|
SourceID: 0,
|
|
Email: &empty,
|
|
})
|
|
resp := MakeRequest(t, req, http.StatusUnprocessableEntity)
|
|
|
|
errMap := make(map[string]any)
|
|
json.Unmarshal(resp.Body.Bytes(), &errMap)
|
|
assert.EqualValues(t, "email is not allowed to be empty string", errMap["message"].(string))
|
|
|
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{LoginName: "user2"})
|
|
assert.False(t, user2.IsRestricted)
|
|
bTrue := true
|
|
req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{
|
|
// required
|
|
LoginName: "user2",
|
|
SourceID: 0,
|
|
// to change
|
|
Restricted: &bTrue,
|
|
})
|
|
MakeRequest(t, req, http.StatusOK)
|
|
user2 = unittest.AssertExistsAndLoadBean(t, &user_model.User{LoginName: "user2"})
|
|
assert.True(t, user2.IsRestricted)
|
|
}
|
|
|
|
func TestAPICreateRepoForUser(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
adminUsername := "user1"
|
|
token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
|
|
|
|
req := NewRequestWithJSON(
|
|
t,
|
|
"POST",
|
|
fmt.Sprintf("/api/v1/admin/users/%s/repos?token=%s", adminUsername, token),
|
|
&api.CreateRepoOption{
|
|
Name: "admincreatedrepo",
|
|
},
|
|
)
|
|
MakeRequest(t, req, http.StatusCreated)
|
|
}
|
|
|
|
func TestAPIRenameUser(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
adminUsername := "user1"
|
|
token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
|
|
urlStr := fmt.Sprintf("/api/v1/admin/users/%s/rename?token=%s", "user2", token)
|
|
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
|
|
// required
|
|
"new_name": "User2",
|
|
})
|
|
MakeRequest(t, req, http.StatusOK)
|
|
|
|
urlStr = fmt.Sprintf("/api/v1/admin/users/%s/rename?token=%s", "User2", token)
|
|
req = NewRequestWithValues(t, "POST", urlStr, map[string]string{
|
|
// required
|
|
"new_name": "User2-2-2",
|
|
})
|
|
MakeRequest(t, req, http.StatusOK)
|
|
|
|
urlStr = fmt.Sprintf("/api/v1/admin/users/%s/rename?token=%s", "User2", token)
|
|
req = NewRequestWithValues(t, "POST", urlStr, map[string]string{
|
|
// required
|
|
"new_name": "user1",
|
|
})
|
|
// the old user name still be used by with a redirect
|
|
MakeRequest(t, req, http.StatusTemporaryRedirect)
|
|
|
|
urlStr = fmt.Sprintf("/api/v1/admin/users/%s/rename?token=%s", "User2-2-2", token)
|
|
req = NewRequestWithValues(t, "POST", urlStr, map[string]string{
|
|
// required
|
|
"new_name": "user1",
|
|
})
|
|
MakeRequest(t, req, http.StatusUnprocessableEntity)
|
|
|
|
urlStr = fmt.Sprintf("/api/v1/admin/users/%s/rename?token=%s", "User2-2-2", token)
|
|
req = NewRequestWithValues(t, "POST", urlStr, map[string]string{
|
|
// required
|
|
"new_name": "user2",
|
|
})
|
|
MakeRequest(t, req, http.StatusOK)
|
|
}
|
|
|
|
func TestAPICron(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
// user1 is an admin user
|
|
session := loginUser(t, "user1")
|
|
|
|
t.Run("List", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadAdmin)
|
|
urlStr := fmt.Sprintf("/api/v1/admin/cron?token=%s", token)
|
|
req := NewRequest(t, "GET", urlStr)
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
|
|
assert.Equal(t, "28", resp.Header().Get("X-Total-Count"))
|
|
|
|
var crons []api.Cron
|
|
DecodeJSON(t, resp, &crons)
|
|
assert.Len(t, crons, 28)
|
|
})
|
|
|
|
t.Run("Execute", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
now := time.Now()
|
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin)
|
|
// Archive cleanup is harmless, because in the test environment there are none
|
|
// and is thus an NOOP operation and therefore doesn't interfere with any other
|
|
// tests.
|
|
urlStr := fmt.Sprintf("/api/v1/admin/cron/archive_cleanup?token=%s", token)
|
|
req := NewRequest(t, "POST", urlStr)
|
|
MakeRequest(t, req, http.StatusNoContent)
|
|
|
|
// Check for the latest run time for this cron, to ensure it has been run.
|
|
urlStr = fmt.Sprintf("/api/v1/admin/cron?token=%s", token)
|
|
req = NewRequest(t, "GET", urlStr)
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
|
|
var crons []api.Cron
|
|
DecodeJSON(t, resp, &crons)
|
|
|
|
for _, cron := range crons {
|
|
if cron.Name == "archive_cleanup" {
|
|
assert.True(t, now.Before(cron.Prev))
|
|
}
|
|
}
|
|
})
|
|
}
|