mirror of
https://github.com/apricote/releaser-pleaser.git
synced 2026-02-07 10:17:02 +00:00
test(e2e): introduce e2e test framework with local forgejo
This commit is contained in:
parent
6467f16e58
commit
f4f27ffacd
9 changed files with 336 additions and 1 deletions
29
.github/workflows/ci.yaml
vendored
29
.github/workflows/ci.yaml
vendored
|
|
@ -41,6 +41,35 @@ jobs:
|
||||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5
|
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
flags: unit
|
||||||
|
|
||||||
|
test-e2e-forgejo:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
|
||||||
|
# We can not use "jobs.<job>.services".
|
||||||
|
# We want to mount the config file, which is only available after "Checkout".
|
||||||
|
- name: Start Forgejo
|
||||||
|
working-directory: test/e2e/forgejo
|
||||||
|
run: docker compose up --wait
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: go test -tags e2e_forgejo -v -race -coverpkg=./... -coverprofile=coverage.txt ./test/e2e/forgejo
|
||||||
|
|
||||||
|
|
||||||
|
- name: Upload results to Codecov
|
||||||
|
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
flags: e2e
|
||||||
|
|
||||||
go-mod-tidy:
|
go-mod-tidy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
|
||||||
2
codecov.yaml
Normal file
2
codecov.yaml
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
ignore:
|
||||||
|
- "test"
|
||||||
2
go.mod
2
go.mod
|
|
@ -1,6 +1,6 @@
|
||||||
module github.com/apricote/releaser-pleaser
|
module github.com/apricote/releaser-pleaser
|
||||||
|
|
||||||
go 1.23.2
|
go 1.24
|
||||||
|
|
||||||
toolchain go1.24.6
|
toolchain go1.24.6
|
||||||
|
|
||||||
|
|
|
||||||
19
test/e2e/forge.go
Normal file
19
test/e2e/forge.go
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
package e2e
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestForge interface {
|
||||||
|
Init(ctx context.Context, runID string) error
|
||||||
|
CreateRepo(t *testing.T, opts CreateRepoOpts) (*Repository, error)
|
||||||
|
|
||||||
|
RunArguments() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateRepoOpts struct {
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
DefaultBranch string
|
||||||
|
}
|
||||||
23
test/e2e/forgejo/app.ini
Normal file
23
test/e2e/forgejo/app.ini
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
WORK_PATH = /data/gitea
|
||||||
|
|
||||||
|
[database]
|
||||||
|
DB_TYPE = sqlite3
|
||||||
|
PATH = /data/gitea/forgejo.db
|
||||||
|
|
||||||
|
[security]
|
||||||
|
INSTALL_LOCK = true
|
||||||
|
SECRET_KEY = releaser-pleaser
|
||||||
|
INTERNAL_TOKEN = releaser-pleaser
|
||||||
|
|
||||||
|
[service]
|
||||||
|
REGISTER_EMAIL_CONFIRM = false
|
||||||
|
ENABLE_NOTIFY_MAIL = false
|
||||||
|
DISABLE_REGISTRATION = true
|
||||||
|
|
||||||
|
[server]
|
||||||
|
DOMAIN = localhost
|
||||||
|
HTTP_PORT = 3000
|
||||||
|
ROOT_URL = http://localhost:3000/
|
||||||
|
|
||||||
|
[oauth2]
|
||||||
|
JWT_SECRET = rTD-FL2n_aBB6v4AOcr5lBvwgZ6PSr3HGZAuNH6nMu8
|
||||||
16
test/e2e/forgejo/compose.yaml
Normal file
16
test/e2e/forgejo/compose.yaml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
services:
|
||||||
|
forgejo:
|
||||||
|
image: codeberg.org/forgejo/forgejo:11
|
||||||
|
ports:
|
||||||
|
- '3000:3000'
|
||||||
|
- '222:22'
|
||||||
|
volumes:
|
||||||
|
- data:/data/gitea
|
||||||
|
- ./app.ini:/data/gitea/conf/app.ini:ro
|
||||||
|
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "localhost:3000/api/healthz"]
|
||||||
|
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
data:
|
||||||
111
test/e2e/forgejo/forge.go
Normal file
111
test/e2e/forgejo/forge.go
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
package forgejo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2"
|
||||||
|
|
||||||
|
"github.com/apricote/releaser-pleaser/test/e2e"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TestAPIURL = "http://localhost:3000"
|
||||||
|
|
||||||
|
TestUserNameTemplate = "rp-%s"
|
||||||
|
TestUserPassword = "releaser-pleaser"
|
||||||
|
TestUserEmailTemplate = "releaser-pleaser-%s@example.com"
|
||||||
|
TestTokenName = "rp"
|
||||||
|
TestTokenScopes = "write:user,write:issue,write:repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestForge struct {
|
||||||
|
username string
|
||||||
|
token string
|
||||||
|
client *forgejo.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TestForge) Init(ctx context.Context, runID string) error {
|
||||||
|
if err := f.initUser(ctx, runID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := f.initClient(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TestForge) initUser(ctx context.Context, runID string) error {
|
||||||
|
f.username = fmt.Sprintf(TestUserNameTemplate, runID)
|
||||||
|
|
||||||
|
if output, err := exec.CommandContext(ctx,
|
||||||
|
"docker", "compose", "exec", "--user=1000", "forgejo",
|
||||||
|
"forgejo", "admin", "user", "create",
|
||||||
|
"--username", f.username,
|
||||||
|
"--password", TestUserPassword,
|
||||||
|
"--email", fmt.Sprintf(TestUserEmailTemplate, runID),
|
||||||
|
"--must-change-password=false",
|
||||||
|
).CombinedOutput(); err != nil {
|
||||||
|
slog.Debug("create forgejo user output", "output", output)
|
||||||
|
return fmt.Errorf("failed to create forgejo user: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := exec.CommandContext(ctx,
|
||||||
|
"docker", "compose", "exec", "--user=1000", "forgejo",
|
||||||
|
"forgejo", "admin", "user", "generate-access-token",
|
||||||
|
"--username", f.username,
|
||||||
|
"--token-name", TestTokenName,
|
||||||
|
"--scopes", TestTokenScopes,
|
||||||
|
"--raw",
|
||||||
|
).Output()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create forgejo token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.token = strings.TrimSpace(string(token))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TestForge) initClient(ctx context.Context) (err error) {
|
||||||
|
f.client, err = forgejo.NewClient(TestAPIURL,
|
||||||
|
forgejo.SetToken(f.token),
|
||||||
|
forgejo.SetUserAgent("releaser-pleaser-e2e-tests"),
|
||||||
|
forgejo.SetContext(ctx),
|
||||||
|
// forgejo.SetDebugMode(),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TestForge) CreateRepo(t *testing.T, opts e2e.CreateRepoOpts) (*e2e.Repository, error) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
repo, _, err := f.client.CreateRepo(forgejo.CreateRepoOption{
|
||||||
|
Name: opts.Name,
|
||||||
|
Description: opts.Description,
|
||||||
|
DefaultBranch: opts.DefaultBranch,
|
||||||
|
Readme: "Default",
|
||||||
|
AutoInit: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &e2e.Repository{
|
||||||
|
Name: repo.Name,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TestForge) RunArguments() []string {
|
||||||
|
return []string{"--forge=forgejo",
|
||||||
|
fmt.Sprintf("--owner=%s", f.username),
|
||||||
|
fmt.Sprintf("--api-url=%s", TestAPIURL),
|
||||||
|
fmt.Sprintf("--api-token=%s", f.token),
|
||||||
|
fmt.Sprintf("--username=%s", f.username),
|
||||||
|
}
|
||||||
|
}
|
||||||
39
test/e2e/forgejo/forgejo_test.go
Normal file
39
test/e2e/forgejo/forgejo_test.go
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
//go:build e2e_forgejo
|
||||||
|
|
||||||
|
package forgejo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/apricote/releaser-pleaser/test/e2e"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
f *e2e.Framework
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
f, err = e2e.NewFramework(ctx, &TestForge{})
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to set up test framework", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateRepository(t *testing.T) {
|
||||||
|
_ = f.NewRepository(t, t.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRun(t *testing.T) {
|
||||||
|
repo := f.NewRepository(t, t.Name())
|
||||||
|
require.NoError(t, f.Run(t, repo, []string{}))
|
||||||
|
}
|
||||||
96
test/e2e/framework.go
Normal file
96
test/e2e/framework.go
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
package e2e
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/apricote/releaser-pleaser/cmd/rp/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TestDefaultBranch = "main"
|
||||||
|
)
|
||||||
|
|
||||||
|
func randomString() string {
|
||||||
|
randomBytes := make([]byte, 4)
|
||||||
|
if _, err := rand.Read(randomBytes); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return hex.EncodeToString(randomBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Framework struct {
|
||||||
|
runID string
|
||||||
|
forge TestForge
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFramework(ctx context.Context, forge TestForge) (*Framework, error) {
|
||||||
|
f := &Framework{
|
||||||
|
runID: randomString(),
|
||||||
|
forge: forge,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := forge.Init(ctx, f.runID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Repository struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Framework) NewRepository(t *testing.T, name string) *Repository {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
r := &Repository{
|
||||||
|
Name: fmt.Sprintf("%s-%s-%s", name, f.runID, randomString()),
|
||||||
|
}
|
||||||
|
|
||||||
|
repo, err := f.forge.CreateRepo(t, CreateRepoOpts{
|
||||||
|
Name: r.Name,
|
||||||
|
Description: name,
|
||||||
|
DefaultBranch: TestDefaultBranch,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, repo)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Framework) Run(t *testing.T, r *Repository, extraFiles []string) error {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
ctx := t.Context()
|
||||||
|
|
||||||
|
rootCmd := cmd.NewRootCmd()
|
||||||
|
rootCmd.SetArgs(append([]string{
|
||||||
|
"run",
|
||||||
|
fmt.Sprintf("--repo=%s", r.Name),
|
||||||
|
fmt.Sprintf("--extra-files=%q", strings.Join(extraFiles, "\n")),
|
||||||
|
}, f.forge.RunArguments()...))
|
||||||
|
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
|
||||||
|
rootCmd.SetOut(&stdout)
|
||||||
|
rootCmd.SetErr(&stderr)
|
||||||
|
|
||||||
|
err := rootCmd.ExecuteContext(ctx)
|
||||||
|
|
||||||
|
stdoutString := stdout.String()
|
||||||
|
stderrString := stderr.String()
|
||||||
|
|
||||||
|
t.Log(stdoutString)
|
||||||
|
t.Log(stderrString)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue