forgejo/models/actions/runner_test.go
Andreas Ahlenstorf 0e6f9439ee chore: increase test coverage of runner management (#10490)
Ensures that admins, users, etc. see the runners they are allowed to see.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10490
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch>
Co-committed-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch>
2025-12-20 15:29:40 +01:00

237 lines
7.4 KiB
Go

// SPDX-License-Identifier: MIT
package actions
import (
"encoding/binary"
"fmt"
"testing"
"time"
auth_model "forgejo.org/models/auth"
"forgejo.org/models/db"
"forgejo.org/models/unittest"
"forgejo.org/modules/timeutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestUpdateSecret checks that ActionRunner.UpdateSecret() sets the Token,
// TokenSalt and TokenHash fields based on the specified token.
func TestUpdateSecret(t *testing.T) {
runner := ActionRunner{}
token := "0123456789012345678901234567890123456789"
err := runner.UpdateSecret(token)
require.NoError(t, err)
assert.Equal(t, token, runner.Token)
assert.Regexp(t, "^[0-9a-f]{32}$", runner.TokenSalt)
assert.Equal(t, runner.TokenHash, auth_model.HashToken(token, runner.TokenSalt))
}
func TestDeleteRunner(t *testing.T) {
const recordID = 12345678
require.NoError(t, unittest.PrepareTestDatabase())
before := unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: recordID})
err := DeleteRunner(db.DefaultContext, &ActionRunner{ID: recordID})
require.NoError(t, err)
var after ActionRunner
found, err := db.GetEngine(db.DefaultContext).ID(recordID).Unscoped().Get(&after)
require.NoError(t, err)
assert.True(t, found)
// Most fields (namely Name, Version, OwnerID, RepoID, Description, Base, RepoRange,
// TokenHash, TokenSalt, LastOnline, LastActive, AgentLabels and Created) are unaffected
assert.Equal(t, before.Name, after.Name)
assert.Equal(t, before.Version, after.Version)
assert.Equal(t, before.OwnerID, after.OwnerID)
assert.Equal(t, before.RepoID, after.RepoID)
assert.Equal(t, before.Description, after.Description)
assert.Equal(t, before.Base, after.Base)
assert.Equal(t, before.RepoRange, after.RepoRange)
assert.Equal(t, before.TokenHash, after.TokenHash)
assert.Equal(t, before.TokenSalt, after.TokenSalt)
assert.Equal(t, before.LastOnline, after.LastOnline)
assert.Equal(t, before.LastActive, after.LastActive)
assert.Equal(t, before.AgentLabels, after.AgentLabels)
assert.Equal(t, before.Created, after.Created)
// Deleted contains a value
assert.NotNil(t, after.Deleted)
// UUID was modified
assert.NotEqual(t, before.UUID, after.UUID)
// UUID starts with ffffffff-ffff-ffff-
assert.Equal(t, "ffffffff-ffff-ffff-", after.UUID[:19])
// UUID ends with LE binary representation of record ID
idAsBinary := make([]byte, 8)
binary.LittleEndian.PutUint64(idAsBinary, uint64(recordID))
idAsHexadecimal := fmt.Sprintf("%.2x%.2x-%.2x%.2x%.2x%.2x%.2x%.2x", idAsBinary[0],
idAsBinary[1], idAsBinary[2], idAsBinary[3], idAsBinary[4], idAsBinary[5],
idAsBinary[6], idAsBinary[7])
assert.Equal(t, idAsHexadecimal, after.UUID[19:])
}
func TestDeleteOfflineRunnersRunnerGlobalOnly(t *testing.T) {
baseTime := time.Date(2024, 5, 19, 7, 40, 32, 0, time.UTC)
timeutil.MockSet(baseTime)
defer timeutil.MockUnset()
require.NoError(t, unittest.PrepareTestDatabase())
olderThan := timeutil.TimeStampNow().Add(-timeutil.Hour)
require.NoError(t, DeleteOfflineRunners(db.DefaultContext, olderThan, true))
// create at test base time
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 12345678})
// last_online test base time
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000001})
// created one month ago but a repo
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000002})
// last online one hour ago
unittest.AssertNotExistsBean(t, &ActionRunner{ID: 10000003})
// last online 10 seconds ago
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000004})
// created 1 month ago
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000005})
// created 1 hour ago
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000006})
// last online 1 hour ago
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000007})
}
func TestDeleteOfflineRunnersAll(t *testing.T) {
baseTime := time.Date(2024, 5, 19, 7, 40, 32, 0, time.UTC)
timeutil.MockSet(baseTime)
defer timeutil.MockUnset()
require.NoError(t, unittest.PrepareTestDatabase())
olderThan := timeutil.TimeStampNow().Add(-timeutil.Hour)
require.NoError(t, DeleteOfflineRunners(db.DefaultContext, olderThan, false))
// create at test base time
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 12345678})
// last_online test base time
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000001})
// created one month ago
unittest.AssertNotExistsBean(t, &ActionRunner{ID: 10000002})
// last online one hour ago
unittest.AssertNotExistsBean(t, &ActionRunner{ID: 10000003})
// last online 10 seconds ago
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000004})
// created 1 month ago
unittest.AssertNotExistsBean(t, &ActionRunner{ID: 10000005})
// created 1 hour ago
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000006})
// last online 1 hour ago
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000007})
}
func TestDeleteOfflineRunnersErrorOnInvalidOlderThanValue(t *testing.T) {
baseTime := time.Date(2024, 5, 19, 7, 40, 32, 0, time.UTC)
timeutil.MockSet(baseTime)
defer timeutil.MockUnset()
require.Error(t, DeleteOfflineRunners(db.DefaultContext, timeutil.TimeStampNow(), false))
}
func TestRunnerEditable(t *testing.T) {
testCases := []struct {
name string
runner *ActionRunner
ownerID int64
repoID int64
editable bool
}{
{
name: "admin-can-edit-global-runner",
runner: &ActionRunner{Name: "global-runner", OwnerID: 0, RepoID: 0},
ownerID: 0,
repoID: 0,
editable: true,
},
{
name: "admin-can-edit-user-runner",
runner: &ActionRunner{Name: "user-runner", OwnerID: 36, RepoID: 0},
ownerID: 0,
repoID: 0,
editable: true,
},
{
name: "admin-can-edit-repository-runner",
runner: &ActionRunner{Name: "user-runner", OwnerID: 0, RepoID: 110},
ownerID: 0,
repoID: 0,
editable: true,
},
{
name: "user-can-edit-its-runner",
runner: &ActionRunner{Name: "user-runner", OwnerID: 469, RepoID: 0},
ownerID: 469,
repoID: 0,
editable: true,
},
{
name: "user-cannot-edit-global-runner",
runner: &ActionRunner{Name: "global-runner", OwnerID: 0, RepoID: 0},
ownerID: 469,
repoID: 0,
editable: false,
},
{
name: "user-cannot-edit-other-users-runner",
runner: &ActionRunner{Name: "user-runner", OwnerID: 892, RepoID: 0},
ownerID: 469,
repoID: 0,
editable: false,
},
{
name: "user-cannot-edit-repo-runner",
runner: &ActionRunner{Name: "repo-runner", OwnerID: 0, RepoID: 151},
ownerID: 469,
repoID: 0,
editable: false,
},
{
name: "repo-can-edit-its-runner",
runner: &ActionRunner{Name: "repo-runner", OwnerID: 0, RepoID: 693},
ownerID: 0,
repoID: 693,
editable: true,
},
{
name: "repo-cannot-edit-other-repo-runner",
runner: &ActionRunner{Name: "repo-runner", OwnerID: 0, RepoID: 519},
ownerID: 0,
repoID: 693,
editable: false,
},
{
name: "repo-cannot-edit-global-runner",
runner: &ActionRunner{Name: "global-runner", OwnerID: 0, RepoID: 0},
ownerID: 0,
repoID: 693,
editable: false,
},
{
name: "repo-cannot-edit-user-runner",
runner: &ActionRunner{Name: "user-runner", OwnerID: 6, RepoID: 0},
ownerID: 0,
repoID: 693,
editable: false,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
result := testCase.runner.Editable(testCase.ownerID, testCase.repoID)
assert.Equal(t, testCase.editable, result)
})
}
}