forgejo/services/convert/convert_test.go
Andreas Ahlenstorf ddd4cf0d28 chore: revise runner REST API endpoints (#10450)
In https://codeberg.org/forgejo/forgejo/pulls/9409, REST API endpoints were added to manage runners. The REST API endpoints were modelled after GitHub's REST API. That comes at the cost of introducing methods and fields that Forgejo does not and is unlikely to support in the future, like label IDs or label types. But Forgejo would have to maintain them for a very long time.

The introduced endpoints have been revised and aligned with existing Forgejo REST API endpoints:

* POST for `/registration-token` has been removed because it was only an alias of GET.
* `/runners` returns a list of `ActionRunner` instead of a wrapper object. `total_count` was replaced with the header `x-total-count` that is used throughout Forgejo.
* `status` in `ActionRunner` was converted to an enum that is documented.
* `busy` in `ActionRunner` was combined with `status`. A single enum is easier to extend and consume.
* `labels` in `ActionRunner` was converted to a list of strings to match existing Forgejo REST API endpoints.
* `ephemeral` has been removed from `ActionRunner` because ephemeral runners have not been merged, yet.
*  `ActionRunner` received a number of new fields: `uuid`, `version`, `description`, `owner_id`, and `repo_id`.

In addition to those structural changes, the test coverage was enhanced and the API documentation polished.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests

- I added test coverage for Go changes...
  - [x] in their respective `*_test.go` for unit tests.
  - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I added test coverage for JavaScript changes...
  - [ ] in `web_src/js/*.test.js` if it can be unit tested.
  - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] I do not want this change to show in the release notes.
- [ ] I want the title to show in the release notes with a link to this pull request.
- [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10450
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch>
Co-committed-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch>
2025-12-21 17:21:02 +01:00

181 lines
7 KiB
Go

// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package convert
import (
"testing"
actions_model "forgejo.org/models/actions"
"forgejo.org/models/db"
"forgejo.org/models/unittest"
user_model "forgejo.org/models/user"
"forgejo.org/modules/git"
api "forgejo.org/modules/structs"
"forgejo.org/modules/timeutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestToVerification(t *testing.T) {
defer unittest.OverrideFixtures("models/fixtures/TestParseCommitWithSSHSignature")()
require.NoError(t, unittest.PrepareTestDatabase())
// Change the user's primary email address to ensure this value isn't ambiguous with any other return value from
// signature verification.
userModel := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
userModel.Email = "secret-email@example.com"
db.GetEngine(t.Context()).ID(userModel.ID).Cols("email").Update(userModel)
t.Run("SSH Key Signature", func(t *testing.T) {
commit := &git.Commit{
Committer: &git.Signature{
Email: "user2@example.com",
},
Signature: &git.ObjectSignature{
Payload: `tree 853694aae8816094a0d875fee7ea26278dbf5d0f
parent c2780d5c313da2a947eae22efd7dacf4213f4e7f
author user2 <user2@example.com> 1699707877 +0100
committer user2 <user2@example.com> 1699707877 +0100
Add content
`,
Signature: `-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgoGSe9Zy7Ez9bSJcaTNjh/Y7p95
f5DujjqkpzFRtw6CEAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
AAAAQBe2Fwk/FKY3SBCnG6jSYcO6ucyahp2SpQ/0P+otslzIHpWNW8cQ0fGLdhhaFynJXQ
fs9cMpZVM9BfIKNUSO8QY=
-----END SSH SIGNATURE-----
`,
},
}
commitVerification := ToVerification(t.Context(), commit)
require.NotNil(t, commitVerification)
assert.Equal(t, &api.PayloadCommitVerification{
Verified: true,
Reason: "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4",
Signature: "-----BEGIN SSH SIGNATURE-----\nU1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgoGSe9Zy7Ez9bSJcaTNjh/Y7p95\nf5DujjqkpzFRtw6CEAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5\nAAAAQBe2Fwk/FKY3SBCnG6jSYcO6ucyahp2SpQ/0P+otslzIHpWNW8cQ0fGLdhhaFynJXQ\nfs9cMpZVM9BfIKNUSO8QY=\n-----END SSH SIGNATURE-----\n",
Signer: &api.PayloadUser{
Name: "user2",
Email: "user2@example.com", // expected email will match the commit's committer's email, regardless of `KeepEmailPrivate`.
},
Payload: "tree 853694aae8816094a0d875fee7ea26278dbf5d0f\nparent c2780d5c313da2a947eae22efd7dacf4213f4e7f\nauthor user2 <user2@example.com> 1699707877 +0100\ncommitter user2 <user2@example.com> 1699707877 +0100\n\nAdd content\n",
}, commitVerification)
})
t.Run("GPG Signature", func(t *testing.T) {
commit := &git.Commit{
ID: git.MustIDFromString("e20aa0bcd2878f65a93de68a3eed9045d6efdd74"),
Committer: &git.Signature{
Email: "user2@example.com",
},
Signature: &git.ObjectSignature{
Payload: `tree e20aa0bcd2878f65a93de68a3eed9045d6efdd74
parent 5cd9b9847563eb730d63d23c1f1b84868e52ae7d
author user2 <user2+committer@example.com> 1759956520 -0600
committer user2 <user2+committer@example.com> 1759956520 -0600
Add content
`,
Signature: `-----BEGIN PGP SIGNATURE-----
iQEzBAABCgAdFiEEdlqhn25IEoMmvK5vmDaXTfEZWRMFAmjmzigACgkQmDaXTfEZ
WROC4ggAs8mD8csA6FV5e2v/4HcxuaZKCN+D8Gvku2JUigODQCA+NOX0FF2jDnCh
tXylBPB4HJw1spKkDLtOpnCUSOniBdl9NcZjnBt6sP/OSnEfLznXFra+9fCHzsu0
9uhDn3Wn1iHWXQ2ZglUwVS0ja6pNgEip8wNZBysv8+XbO1CEEW0m7zQA6tunzIwp
yiPZDUJrKtpKAK0+v19EccT2VjYAa+Vo+p3/E0piaTYNbsTqtFRy63tdjDkf+mo+
l/PaPhrMqdnbxv3/sd/63VCNdvPH3f0+OuydcC7mXyysmvap99EC+QKnpsrm7RAP
uf51WIBywxztet6vi+jYJK1jFoY4iA==
=Lnrt
-----END PGP SIGNATURE-----`,
},
}
commitVerification := ToVerification(t.Context(), commit)
require.NotNil(t, commitVerification)
assert.Equal(t, &api.PayloadCommitVerification{
Verified: true,
Reason: "user2 / 9836974DF1195913",
Signature: "-----BEGIN PGP SIGNATURE-----\n\niQEzBAABCgAdFiEEdlqhn25IEoMmvK5vmDaXTfEZWRMFAmjmzigACgkQmDaXTfEZ\nWROC4ggAs8mD8csA6FV5e2v/4HcxuaZKCN+D8Gvku2JUigODQCA+NOX0FF2jDnCh\ntXylBPB4HJw1spKkDLtOpnCUSOniBdl9NcZjnBt6sP/OSnEfLznXFra+9fCHzsu0\n9uhDn3Wn1iHWXQ2ZglUwVS0ja6pNgEip8wNZBysv8+XbO1CEEW0m7zQA6tunzIwp\nyiPZDUJrKtpKAK0+v19EccT2VjYAa+Vo+p3/E0piaTYNbsTqtFRy63tdjDkf+mo+\nl/PaPhrMqdnbxv3/sd/63VCNdvPH3f0+OuydcC7mXyysmvap99EC+QKnpsrm7RAP\nuf51WIBywxztet6vi+jYJK1jFoY4iA==\n=Lnrt\n-----END PGP SIGNATURE-----",
Signer: &api.PayloadUser{
Name: "user2",
Email: "user2+signingkey@example.com", // expected email will match the signing key's email
},
Payload: "tree e20aa0bcd2878f65a93de68a3eed9045d6efdd74\nparent 5cd9b9847563eb730d63d23c1f1b84868e52ae7d\nauthor user2 <user2+committer@example.com> 1759956520 -0600\ncommitter user2 <user2+committer@example.com> 1759956520 -0600\n\nAdd content\n",
}, commitVerification)
})
}
func TestToActionRunner(t *testing.T) {
testCases := []struct {
name string
runner actions_model.ActionRunner
expectedStatus api.RunnerStatus
}{
{
name: "active-runner",
runner: actions_model.ActionRunner{
ID: 846,
UUID: "0bf6d33b-9be8-4bb3-a210-351ae7f3d48e",
OwnerID: 204958,
RepoID: 0,
Name: "active-example",
Version: "12.1.2",
Description: "A very busy runner",
AgentLabels: []string{"debian", "gpu"},
LastOnline: timeutil.TimeStampNow(),
LastActive: timeutil.TimeStampNow(),
},
expectedStatus: api.RunnerStatusActive,
},
{
name: "offline-runner",
runner: actions_model.ActionRunner{
ID: 731,
UUID: "29b075f8-cd54-4dc2-b1e2-db303b32b0ce",
OwnerID: 0,
RepoID: 255289,
Name: "offline-example",
Version: "dev",
Description: "",
AgentLabels: []string{},
LastOnline: 0,
LastActive: 0,
},
expectedStatus: api.RunnerStatusOffline,
},
{
name: "idle-runner",
runner: actions_model.ActionRunner{
ID: 117,
UUID: "865ca613-f258-49bc-a986-1037ace1ca35",
OwnerID: 39115,
RepoID: 0,
Name: "idle-example",
Version: "11.3.1",
Description: "A runner twiddling its thumbs",
AgentLabels: []string{"docker"},
LastOnline: timeutil.TimeStampNow(),
LastActive: timeutil.TimeStampNow().AddDuration(-actions_model.RunnerIdleTime),
},
expectedStatus: api.RunnerStatusIdle,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
actionRunner, err := ToActionRunner(&testCase.runner)
require.NoError(t, err)
assert.Equal(t, testCase.runner.ID, actionRunner.ID)
assert.Equal(t, testCase.runner.Name, actionRunner.Name)
assert.Equal(t, testCase.runner.UUID, actionRunner.UUID)
assert.Equal(t, testCase.runner.OwnerID, actionRunner.OwnerID)
assert.Equal(t, testCase.runner.RepoID, actionRunner.RepoID)
assert.Equal(t, testCase.runner.Version, actionRunner.Version)
assert.Equal(t, testCase.runner.Description, actionRunner.Description)
assert.Equal(t, testCase.expectedStatus.String(), actionRunner.Status)
assert.Equal(t, testCase.runner.AgentLabels, actionRunner.Labels)
})
}
}