Use DataDir/builds instead of /tmp for clone directories so that bind mounts work correctly when upaas itself runs in a Docker container. The /tmp directory inside the upaas container isn't accessible to the Docker daemon on the host, causing bind mount failures. Also fix test setups to pass Config to deploy service and add delay to webhook test to avoid temp directory cleanup race with async deployment goroutine.
335 lines
8.4 KiB
Go
335 lines
8.4 KiB
Go
package webhook_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/fx"
|
|
|
|
"git.eeqj.de/sneak/upaas/internal/config"
|
|
"git.eeqj.de/sneak/upaas/internal/database"
|
|
"git.eeqj.de/sneak/upaas/internal/docker"
|
|
"git.eeqj.de/sneak/upaas/internal/globals"
|
|
"git.eeqj.de/sneak/upaas/internal/logger"
|
|
"git.eeqj.de/sneak/upaas/internal/models"
|
|
"git.eeqj.de/sneak/upaas/internal/service/deploy"
|
|
"git.eeqj.de/sneak/upaas/internal/service/notify"
|
|
"git.eeqj.de/sneak/upaas/internal/service/webhook"
|
|
)
|
|
|
|
type testDeps struct {
|
|
logger *logger.Logger
|
|
config *config.Config
|
|
db *database.Database
|
|
tmpDir string
|
|
}
|
|
|
|
func setupTestDeps(t *testing.T) *testDeps {
|
|
t.Helper()
|
|
|
|
tmpDir := t.TempDir()
|
|
|
|
globals.SetAppname("upaas-test")
|
|
globals.SetVersion("test")
|
|
|
|
globalsInst, err := globals.New(fx.Lifecycle(nil))
|
|
require.NoError(t, err)
|
|
|
|
loggerInst, err := logger.New(fx.Lifecycle(nil), logger.Params{Globals: globalsInst})
|
|
require.NoError(t, err)
|
|
|
|
cfg := &config.Config{Port: 8080, DataDir: tmpDir, SessionSecret: "test-secret-key-at-least-32-chars"}
|
|
|
|
dbInst, err := database.New(fx.Lifecycle(nil), database.Params{Logger: loggerInst, Config: cfg})
|
|
require.NoError(t, err)
|
|
|
|
return &testDeps{logger: loggerInst, config: cfg, db: dbInst, tmpDir: tmpDir}
|
|
}
|
|
|
|
func setupTestService(t *testing.T) (*webhook.Service, *database.Database, func()) {
|
|
t.Helper()
|
|
|
|
deps := setupTestDeps(t)
|
|
|
|
dockerClient, err := docker.New(fx.Lifecycle(nil), docker.Params{Logger: deps.logger, Config: deps.config})
|
|
require.NoError(t, err)
|
|
|
|
notifySvc, err := notify.New(fx.Lifecycle(nil), notify.ServiceParams{Logger: deps.logger})
|
|
require.NoError(t, err)
|
|
|
|
deploySvc, err := deploy.New(fx.Lifecycle(nil), deploy.ServiceParams{
|
|
Logger: deps.logger, Config: deps.config, Database: deps.db, Docker: dockerClient, Notify: notifySvc,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
svc, err := webhook.New(fx.Lifecycle(nil), webhook.ServiceParams{
|
|
Logger: deps.logger, Database: deps.db, Deploy: deploySvc,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// t.TempDir() automatically cleans up after test
|
|
return svc, deps.db, func() {}
|
|
}
|
|
|
|
func createTestApp(
|
|
t *testing.T,
|
|
dbInst *database.Database,
|
|
branch string,
|
|
) *models.App {
|
|
t.Helper()
|
|
|
|
app := models.NewApp(dbInst)
|
|
app.ID = "test-app-id"
|
|
app.Name = "test-app"
|
|
app.RepoURL = "git@gitea.example.com:user/repo.git"
|
|
app.Branch = branch
|
|
app.DockerfilePath = "Dockerfile"
|
|
app.WebhookSecret = "webhook-secret-123"
|
|
app.SSHPrivateKey = "private-key"
|
|
app.SSHPublicKey = "public-key"
|
|
app.Status = models.AppStatusPending
|
|
|
|
err := app.Save(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
return app
|
|
}
|
|
|
|
func TestExtractBranch(testingT *testing.T) {
|
|
testingT.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
ref string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "extracts main branch",
|
|
ref: "refs/heads/main",
|
|
expected: "main",
|
|
},
|
|
{
|
|
name: "extracts feature branch",
|
|
ref: "refs/heads/feature/new-feature",
|
|
expected: "feature/new-feature",
|
|
},
|
|
{
|
|
name: "extracts develop branch",
|
|
ref: "refs/heads/develop",
|
|
expected: "develop",
|
|
},
|
|
{
|
|
name: "returns raw ref if no prefix",
|
|
ref: "main",
|
|
expected: "main",
|
|
},
|
|
{
|
|
name: "handles empty ref",
|
|
ref: "",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "handles partial prefix",
|
|
ref: "refs/heads/",
|
|
expected: "",
|
|
},
|
|
}
|
|
|
|
for _, testCase := range tests {
|
|
testingT.Run(testCase.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// We test via HandleWebhook since extractBranch is not exported.
|
|
// The test verifies behavior indirectly through the webhook event's branch.
|
|
svc, dbInst, cleanup := setupTestService(t)
|
|
defer cleanup()
|
|
|
|
app := createTestApp(t, dbInst, testCase.expected)
|
|
|
|
payload := []byte(`{"ref": "` + testCase.ref + `"}`)
|
|
|
|
err := svc.HandleWebhook(context.Background(), app, "push", payload)
|
|
require.NoError(t, err)
|
|
|
|
events, err := app.GetWebhookEvents(context.Background(), 10)
|
|
require.NoError(t, err)
|
|
require.Len(t, events, 1)
|
|
|
|
assert.Equal(t, testCase.expected, events[0].Branch)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHandleWebhookMatchingBranch(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
svc, dbInst, cleanup := setupTestService(t)
|
|
defer cleanup()
|
|
|
|
app := createTestApp(t, dbInst, "main")
|
|
|
|
payload := []byte(`{
|
|
"ref": "refs/heads/main",
|
|
"before": "0000000000000000000000000000000000000000",
|
|
"after": "abc123def456",
|
|
"repository": {
|
|
"full_name": "user/repo",
|
|
"clone_url": "https://gitea.example.com/user/repo.git",
|
|
"ssh_url": "git@gitea.example.com:user/repo.git"
|
|
},
|
|
"pusher": {"username": "testuser", "email": "test@example.com"},
|
|
"commits": [{"id": "abc123def456", "message": "Test commit",
|
|
"author": {"name": "Test User", "email": "test@example.com"}}]
|
|
}`)
|
|
|
|
err := svc.HandleWebhook(context.Background(), app, "push", payload)
|
|
require.NoError(t, err)
|
|
|
|
events, err := app.GetWebhookEvents(context.Background(), 10)
|
|
require.NoError(t, err)
|
|
require.Len(t, events, 1)
|
|
|
|
event := events[0]
|
|
assert.Equal(t, "push", event.EventType)
|
|
assert.Equal(t, "main", event.Branch)
|
|
assert.True(t, event.Matched)
|
|
assert.Equal(t, "abc123def456", event.CommitSHA.String)
|
|
}
|
|
|
|
func TestHandleWebhookNonMatchingBranch(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
svc, dbInst, cleanup := setupTestService(t)
|
|
defer cleanup()
|
|
|
|
app := createTestApp(t, dbInst, "main")
|
|
|
|
payload := []byte(`{"ref": "refs/heads/develop", "after": "def789ghi012"}`)
|
|
|
|
err := svc.HandleWebhook(context.Background(), app, "push", payload)
|
|
require.NoError(t, err)
|
|
|
|
events, err := app.GetWebhookEvents(context.Background(), 10)
|
|
require.NoError(t, err)
|
|
require.Len(t, events, 1)
|
|
|
|
assert.Equal(t, "develop", events[0].Branch)
|
|
assert.False(t, events[0].Matched)
|
|
}
|
|
|
|
func TestHandleWebhookInvalidJSON(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
svc, dbInst, cleanup := setupTestService(t)
|
|
defer cleanup()
|
|
|
|
app := createTestApp(t, dbInst, "main")
|
|
|
|
err := svc.HandleWebhook(context.Background(), app, "push", []byte(`{invalid json}`))
|
|
require.NoError(t, err)
|
|
|
|
events, err := app.GetWebhookEvents(context.Background(), 10)
|
|
require.NoError(t, err)
|
|
require.Len(t, events, 1)
|
|
}
|
|
|
|
func TestHandleWebhookEmptyPayload(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
svc, dbInst, cleanup := setupTestService(t)
|
|
defer cleanup()
|
|
|
|
app := createTestApp(t, dbInst, "main")
|
|
|
|
err := svc.HandleWebhook(context.Background(), app, "push", []byte(`{}`))
|
|
require.NoError(t, err)
|
|
|
|
events, err := app.GetWebhookEvents(context.Background(), 10)
|
|
require.NoError(t, err)
|
|
require.Len(t, events, 1)
|
|
assert.False(t, events[0].Matched)
|
|
}
|
|
|
|
func TestGiteaPushPayloadParsing(testingT *testing.T) {
|
|
testingT.Parallel()
|
|
|
|
testingT.Run("parses full payload", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
payload := []byte(`{
|
|
"ref": "refs/heads/main",
|
|
"before": "0000000000000000000000000000000000000000",
|
|
"after": "abc123def456789",
|
|
"repository": {
|
|
"full_name": "myorg/myrepo",
|
|
"clone_url": "https://gitea.example.com/myorg/myrepo.git",
|
|
"ssh_url": "git@gitea.example.com:myorg/myrepo.git"
|
|
},
|
|
"pusher": {
|
|
"username": "developer",
|
|
"email": "dev@example.com"
|
|
},
|
|
"commits": [
|
|
{
|
|
"id": "abc123def456789",
|
|
"message": "Fix bug in feature",
|
|
"author": {
|
|
"name": "Developer",
|
|
"email": "dev@example.com"
|
|
}
|
|
},
|
|
{
|
|
"id": "def456789abc123",
|
|
"message": "Add tests",
|
|
"author": {
|
|
"name": "Developer",
|
|
"email": "dev@example.com"
|
|
}
|
|
}
|
|
]
|
|
}`)
|
|
|
|
var pushPayload webhook.GiteaPushPayload
|
|
|
|
err := json.Unmarshal(payload, &pushPayload)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "refs/heads/main", pushPayload.Ref)
|
|
assert.Equal(t, "abc123def456789", pushPayload.After)
|
|
assert.Equal(t, "myorg/myrepo", pushPayload.Repository.FullName)
|
|
assert.Equal(
|
|
t,
|
|
"git@gitea.example.com:myorg/myrepo.git",
|
|
pushPayload.Repository.SSHURL,
|
|
)
|
|
assert.Equal(t, "developer", pushPayload.Pusher.Username)
|
|
assert.Len(t, pushPayload.Commits, 2)
|
|
assert.Equal(t, "Fix bug in feature", pushPayload.Commits[0].Message)
|
|
})
|
|
}
|
|
|
|
// TestSetupTestService verifies the test helper creates a working test service.
|
|
func TestSetupTestService(testingT *testing.T) {
|
|
testingT.Parallel()
|
|
|
|
testingT.Run("creates working test service", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
svc, dbInst, cleanup := setupTestService(t)
|
|
defer cleanup()
|
|
|
|
require.NotNil(t, svc)
|
|
require.NotNil(t, dbInst)
|
|
|
|
// Verify database is working
|
|
tmpDir := filepath.Dir(dbInst.Path())
|
|
_, err := os.Stat(tmpDir)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|