mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2026-03-16 01:19:02 +01:00
next
This commit is contained in:
60
test/integration/blocks/git_repo.go
Normal file
60
test/integration/blocks/git_repo.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package blocks
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/test/integration/utils"
|
||||
)
|
||||
|
||||
type gitRepo struct {
|
||||
folder string
|
||||
}
|
||||
|
||||
func NewGitRepo(t *testing.T) *gitRepo {
|
||||
return &gitRepo{
|
||||
folder: t.TempDir(),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *gitRepo) Clone(t *testing.T, remoteURL string) {
|
||||
utils.NewCommand("git", "clone", remoteURL, r.folder).RunOrFail(t)
|
||||
}
|
||||
|
||||
func (r *gitRepo) Init(t *testing.T, remoteURL string) {
|
||||
utils.NewCommand("git", "init").RunOrFail(t)
|
||||
utils.NewCommand("git", "remote", "add", remoteURL).RunOrFail(t)
|
||||
utils.NewCommand("git", "branch", "set-upstream-to=origin/main").RunOrFail(t)
|
||||
}
|
||||
|
||||
func (r *gitRepo) InitFromTemplate(t *testing.T, templatePath, remoteURL string) {
|
||||
utils.NewCommand("cp", "-r", templatePath, r.folder).RunOrFail(t)
|
||||
r.Init(t, remoteURL)
|
||||
}
|
||||
|
||||
func (r *gitRepo) Add(t *testing.T, filePath string) {
|
||||
utils.NewCommand("git", "add", filePath).RunOrFail(t)
|
||||
}
|
||||
|
||||
func (r *gitRepo) Commit(t *testing.T, message string) {
|
||||
utils.NewCommand("git", "commit", "-m", message).RunOrFail(t)
|
||||
}
|
||||
|
||||
func (r *gitRepo) Push(t *testing.T) {
|
||||
utils.NewCommand("git", "push").RunOrFail(t)
|
||||
}
|
||||
|
||||
func (r *gitRepo) Tag(t *testing.T, name, message string) {
|
||||
utils.NewCommand("git", "tag", "-a", name, "-m", message).RunOrFail(t)
|
||||
}
|
||||
|
||||
func (r *gitRepo) WriteFile(t *testing.T, filePath string, content []byte) {
|
||||
// Ensure the directory exists
|
||||
os.MkdirAll(path.Join(r.folder, path.Dir(filePath)), 0755)
|
||||
|
||||
err := os.WriteFile(path.Join(r.folder, filePath), content, 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to write file %s: %v", filePath, err)
|
||||
}
|
||||
}
|
||||
26
test/integration/blocks/repo.go
Normal file
26
test/integration/blocks/repo.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package blocks
|
||||
|
||||
import "testing"
|
||||
|
||||
type TestRepo struct {
|
||||
}
|
||||
|
||||
func NewTestRepo() *TestRepo {
|
||||
return &TestRepo{}
|
||||
}
|
||||
|
||||
func (r *TestRepo) Enable(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func (r *TestRepo) Repair(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func (r *TestRepo) Disable(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func (r *TestRepo) Delete(t *testing.T) {
|
||||
|
||||
}
|
||||
23
test/integration/blocks/secret.go
Normal file
23
test/integration/blocks/secret.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package blocks
|
||||
|
||||
import "testing"
|
||||
|
||||
type TestSecret struct {
|
||||
repo *TestRepo
|
||||
}
|
||||
|
||||
func NewTestSecret(repo *TestRepo) *TestSecret {
|
||||
return &TestSecret{repo: repo}
|
||||
}
|
||||
|
||||
func (s *TestSecret) Create(t *testing.T, key, value string) {
|
||||
|
||||
}
|
||||
|
||||
func (s *TestSecret) Update(t *testing.T, value string) {
|
||||
|
||||
}
|
||||
|
||||
func (s *TestSecret) Delete(t *testing.T) {
|
||||
|
||||
}
|
||||
69
test/integration/env/agent.go
vendored
Normal file
69
test/integration/env/agent.go
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/test/integration/utils"
|
||||
)
|
||||
|
||||
func (e *TestEnv) StartAgent(serverURL, agentToken string) error {
|
||||
t := e.t
|
||||
|
||||
if e.Agent != nil {
|
||||
return fmt.Errorf("agent already started")
|
||||
}
|
||||
|
||||
t.Log(" 🤖 Starting Woodpecker Agent with mock backend...")
|
||||
|
||||
service := utils.NewService("go", "run", "./cmd/agent/").
|
||||
WorkDir(e.projectRoot).
|
||||
// Agent configuration
|
||||
SetEnv("WOODPECKER_SERVER", serverURL).
|
||||
SetEnv("WOODPECKER_AGENT_SECRET", agentToken).
|
||||
// SetEnv("WOODPECKER_MAX_WORKFLOWS", "1").
|
||||
// SetEnv("WOODPECKER_HEALTHCHECK", "false").
|
||||
SetEnv("WOODPECKER_BACKEND", "dummy").
|
||||
// Log level
|
||||
SetEnv("WOODPECKER_LOG_LEVEL", "debug")
|
||||
|
||||
if err := service.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start agent: %w", err)
|
||||
}
|
||||
|
||||
t.Cleanup(e.StopAgent)
|
||||
|
||||
e.Agent = service
|
||||
|
||||
// TODO: wait for agent to be ready
|
||||
// if err := utils.WaitForHTTP("http://localhost:3000", 30*time.Second); err != nil {
|
||||
// return fmt.Errorf("forge did not become ready: %w", err)
|
||||
// }
|
||||
|
||||
t.Logf(" ✓ Woodpecker Agent started successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *TestEnv) StopAgent() {
|
||||
t := e.t
|
||||
if e.Agent != nil {
|
||||
if err := e.Agent.Stop(); err != nil {
|
||||
t.Errorf("Warning: Failed to stop agent: %v", err)
|
||||
} else {
|
||||
t.Logf("Woodpecker agent stopped successfully")
|
||||
}
|
||||
}
|
||||
}
|
||||
130
test/integration/env/env.go
vendored
Normal file
130
test/integration/env/env.go
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/test/integration/utils"
|
||||
)
|
||||
|
||||
// TestEnv represents the complete integration test environment
|
||||
// with all necessary components (Forge, Woodpecker Server, Woodpecker Agent)
|
||||
type TestEnv struct {
|
||||
t *testing.T
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
projectRoot string
|
||||
|
||||
// Components
|
||||
Forge *TestForge
|
||||
Server *TestServer
|
||||
Agent *utils.Service
|
||||
|
||||
// API Clients
|
||||
GiteaClient *GiteaClient
|
||||
WoodpeckerClient *utils.WoodpeckerClient
|
||||
}
|
||||
|
||||
func SetupTestEnv(t *testing.T) *TestEnv {
|
||||
t.Helper()
|
||||
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
|
||||
env := &TestEnv{
|
||||
t: t,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
func (e *TestEnv) Start() {
|
||||
t := e.t
|
||||
t.Helper()
|
||||
|
||||
t.Log("🚀 Setting up integration test environment...")
|
||||
|
||||
// Step 1: Start Forge (Gitea)
|
||||
e.Forge = NewTestForge()
|
||||
|
||||
err := e.Forge.Start(t)
|
||||
if err != nil {
|
||||
e.Stop()
|
||||
t.Fatalf("Failed to start forge: %v", err)
|
||||
}
|
||||
|
||||
giteaClient := "test-client"
|
||||
giteaClientSecret := "test-secret"
|
||||
|
||||
e.GiteaClient = NewGiteaClient(e.Forge.URL, e.Forge.AdminToken)
|
||||
|
||||
// Step 2: Start Woodpecker Server
|
||||
t.Log(" 🔧 Starting Woodpecker Server...")
|
||||
e.Server = &TestServer{
|
||||
URL: "http://localhost:8000",
|
||||
}
|
||||
err = e.Server.Start(t, e.Forge.URL, giteaClient, giteaClientSecret)
|
||||
if err != nil {
|
||||
e.Stop()
|
||||
t.Fatalf("Failed to start Woodpecker Server: %v", err)
|
||||
}
|
||||
|
||||
// woodpeckerURL := "http://localhost:8000"
|
||||
woodpeckerGRPC_URL := "http://localhost:9000"
|
||||
woodpeckerAgentToken := "woodpecker-agent-token"
|
||||
|
||||
// Step 3: Start Woodpecker Agent with dummy backend
|
||||
err = e.StartAgent(woodpeckerGRPC_URL, woodpeckerAgentToken)
|
||||
if err != nil {
|
||||
e.Stop()
|
||||
t.Fatalf("Failed to start Woodpecker Agent: %v", err)
|
||||
}
|
||||
|
||||
t.Log("✅ Integration test environment setup complete!")
|
||||
}
|
||||
|
||||
func (e *TestEnv) Stop() {
|
||||
t := e.t
|
||||
|
||||
t.Helper()
|
||||
t.Log("🧹 Cleaning up test environment...")
|
||||
|
||||
if e.cancel != nil {
|
||||
e.cancel()
|
||||
}
|
||||
|
||||
if e.Agent != nil {
|
||||
e.Agent.Stop()
|
||||
}
|
||||
|
||||
if e.Server != nil {
|
||||
e.Server.Stop()
|
||||
}
|
||||
|
||||
if e.Forge != nil {
|
||||
e.Forge.Stop()
|
||||
}
|
||||
|
||||
t.Log("✓ Cleanup complete")
|
||||
}
|
||||
133
test/integration/env/forge.go
vendored
Normal file
133
test/integration/env/forge.go
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build test
|
||||
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/test/integration/utils"
|
||||
)
|
||||
|
||||
type TestForge struct {
|
||||
URL string
|
||||
AdminUser string
|
||||
AdminPassword string
|
||||
AdminEmail string
|
||||
AdminToken string
|
||||
|
||||
service *utils.Service
|
||||
}
|
||||
|
||||
func NewTestForge() *TestForge {
|
||||
return &TestForge{
|
||||
URL: "http://localhost:8000",
|
||||
AdminUser: "woodpecker",
|
||||
AdminPassword: "woodpecker123",
|
||||
AdminEmail: "woodpecker@localhost",
|
||||
}
|
||||
}
|
||||
|
||||
func (f *TestForge) Start(t *testing.T) error {
|
||||
if f.service != nil {
|
||||
return fmt.Errorf("forge already started")
|
||||
}
|
||||
|
||||
projectRoot := "."
|
||||
|
||||
t.Log(" 📦 Starting forge (Gitea) ...")
|
||||
|
||||
composeFile := filepath.Join(projectRoot, "data", "gitea", "docker-compose.yml")
|
||||
f.service = utils.NewService("docker", "compose", "-f", composeFile, "up", "-d")
|
||||
if err := f.service.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start forge: %w", err)
|
||||
}
|
||||
|
||||
// Wait for Gitea to be ready
|
||||
if err := utils.WaitForHTTP("http://localhost:3000", 30*time.Second); err != nil {
|
||||
return fmt.Errorf("forge did not become ready: %w", err)
|
||||
}
|
||||
|
||||
t.Log(" ✓ Forge started successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *TestForge) SetupAdmin(t *testing.T) error {
|
||||
utils.NewCommand("docker", "compose", "exec", "gitea",
|
||||
"gitea", "admin", "user", "create",
|
||||
"--username", f.AdminUser,
|
||||
"--password", f.AdminPassword,
|
||||
"--email", f.AdminEmail,
|
||||
"--admin",
|
||||
).RunOrFail(t)
|
||||
|
||||
adminToken, err := utils.NewCommand("docker", "compose", "exec", "-T", "gitea",
|
||||
"gitea", "admin", "user", "generate-access-token",
|
||||
"-u", f.AdminUser,
|
||||
"--scopes", "write:repository,write:user",
|
||||
"--raw",
|
||||
).Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate admin token: %w", err)
|
||||
}
|
||||
|
||||
f.AdminToken = adminToken
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *TestForge) SetupOAuthApp(t *testing.T, clientName, clientSecret string) error {
|
||||
appID, err := utils.NewCommand("docker", "compose", "exec", "-T", "gitea",
|
||||
"gitea", "admin", "oauth2", "add",
|
||||
"--name", clientName,
|
||||
"--redirect-uris", "http://localhost:8000/callback",
|
||||
"--client-secret", clientSecret,
|
||||
).Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create OAuth app: %w", err)
|
||||
}
|
||||
|
||||
t.Logf(" ✓ OAuth app created with ID: %s", appID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *TestForge) GetRepositoryCloneURL(repo string) (string, error) {
|
||||
u, err := url.Parse(f.URL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid forge URL: %w", err)
|
||||
}
|
||||
u.User = url.UserPassword(f.AdminUser, f.AdminPassword)
|
||||
|
||||
return fmt.Sprintf("%s/%s.git", u.String(), repo), nil
|
||||
}
|
||||
|
||||
func (f *TestForge) Stop() error {
|
||||
if f.service == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := f.service.Stop(); err != nil {
|
||||
return fmt.Errorf("failed to stop forge: %w", err)
|
||||
}
|
||||
|
||||
f.service = nil
|
||||
return nil
|
||||
}
|
||||
182
test/integration/env/gitea_client.go
vendored
Normal file
182
test/integration/env/gitea_client.go
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GiteaClient provides methods to interact with Gitea API
|
||||
type GiteaClient struct {
|
||||
baseURL string
|
||||
token string
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// NewGiteaClient creates a new Gitea API client
|
||||
func NewGiteaClient(baseURL, token string) *GiteaClient {
|
||||
return &GiteaClient{
|
||||
baseURL: baseURL,
|
||||
token: token,
|
||||
client: &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// doRequest performs an HTTP request to Gitea API
|
||||
func (c *GiteaClient) doRequest(method, path string, body any) (*http.Response, error) {
|
||||
var bodyReader io.Reader
|
||||
if body != nil {
|
||||
jsonData, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal request body: %w", err)
|
||||
}
|
||||
bodyReader = bytes.NewBuffer(jsonData)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, c.baseURL+path, bodyReader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
if c.token != "" {
|
||||
req.Header.Set("Authorization", "token "+c.token)
|
||||
}
|
||||
if body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
return c.client.Do(req)
|
||||
}
|
||||
|
||||
// CreateRepository creates a new repository in Gitea
|
||||
func (c *GiteaClient) CreateRepository(name, description string) (map[string]any, error) {
|
||||
body := map[string]any{
|
||||
"name": name,
|
||||
"description": description,
|
||||
"private": false,
|
||||
"auto_init": true, // Initialize with README
|
||||
}
|
||||
|
||||
resp, err := c.doRequest("POST", "/api/v1/user/repos", body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("failed to create repository: status %d, body: %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
var repo map[string]any
|
||||
if err := json.NewDecoder(resp.Body).Decode(&repo); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
// CreateFile creates or updates a file in a repository
|
||||
func (c *GiteaClient) CreateFile(owner, repo, filepath, content, message string) error {
|
||||
body := map[string]any{
|
||||
"content": content, // Should be base64 encoded
|
||||
"message": message,
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, repo, filepath)
|
||||
resp, err := c.doRequest("POST", path, body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("failed to create file: status %d, body: %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TriggerWebhook simulates a push webhook from Gitea to Woodpecker
|
||||
func (c *GiteaClient) TriggerWebhook(webhookURL string, payload map[string]any) error {
|
||||
jsonData, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal webhook payload: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", webhookURL, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create webhook request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Gitea-Event", "push")
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send webhook: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("webhook returned error: status %d, body: %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateWebhook creates a webhook in a Gitea repository
|
||||
func (c *GiteaClient) CreateWebhook(owner, repo, webhookURL string) error {
|
||||
body := map[string]any{
|
||||
"type": "gitea",
|
||||
"active": true,
|
||||
"config": map[string]string{
|
||||
"url": webhookURL,
|
||||
"content_type": "json",
|
||||
},
|
||||
"events": []string{"push", "pull_request", "create", "delete"},
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/hooks", owner, repo)
|
||||
resp, err := c.doRequest("POST", path, body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("failed to create webhook: status %d, body: %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRepository gets repository information
|
||||
func (c *GiteaClient) GetRepository(owner, repo string) (map[string]any, error) {
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s", owner, repo)
|
||||
resp, err := c.doRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("failed to get repository: status %d, body: %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
var repository map[string]any
|
||||
if err := json.NewDecoder(resp.Body).Decode(&repository); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
return repository, nil
|
||||
}
|
||||
95
test/integration/env/server.go
vendored
Normal file
95
test/integration/env/server.go
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/test/integration/utils"
|
||||
)
|
||||
|
||||
type TestServer struct {
|
||||
URL string
|
||||
service *utils.Service
|
||||
}
|
||||
|
||||
func (s *TestServer) Start(t *testing.T, giteaURL, giteaClient, giteaClientSecret string) error {
|
||||
if s.service != nil {
|
||||
return fmt.Errorf("server already started")
|
||||
}
|
||||
|
||||
projectRoot := "."
|
||||
|
||||
t.Log(" 🔧 Starting Woodpecker Server...")
|
||||
|
||||
// Prepare web dist directory
|
||||
utils.NewCommand("mkdir", "-p", filepath.Join(projectRoot, "web/dist")).RunOrFail(t)
|
||||
utils.NewCommand("sh", "-c", fmt.Sprintf("echo test > %s", filepath.Join(projectRoot, "web/dist/index.html"))).RunOrFail(t)
|
||||
|
||||
s.service = utils.NewService("go", "run", "./cmd/server/").
|
||||
WorkDir(projectRoot).
|
||||
// Server configuration
|
||||
SetEnv("WOODPECKER_OPEN", "true").
|
||||
SetEnv("WOODPECKER_ADMIN", "woodpecker").
|
||||
SetEnv("WOODPECKER_HOST", "http://localhost:8000").
|
||||
SetEnv("WOODPECKER_SERVER_ADDR", ":8000").
|
||||
SetEnv("WOODPECKER_GRPC_ADDR", ":9000").
|
||||
SetEnv("WOODPECKER_WEBHOOK_HOST", "http://localhost:8000").
|
||||
SetEnv("WOODPECKER_AGENT_SECRET", "test-secret-123").
|
||||
// Gitea forge configuration
|
||||
SetEnv("WOODPECKER_GITEA", "true").
|
||||
SetEnv("WOODPECKER_GITEA_URL", giteaURL).
|
||||
SetEnv("WOODPECKER_GITEA_CLIENT", giteaClient).
|
||||
SetEnv("WOODPECKER_GITEA_SECRET", giteaClientSecret).
|
||||
// Log level
|
||||
SetEnv("WOODPECKER_LOG_LEVEL", "debug")
|
||||
|
||||
if err := s.service.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start server: %w", err)
|
||||
}
|
||||
|
||||
// Wait for server to be ready
|
||||
if err := utils.WaitForHTTP("http://localhost:8000/healthz", 30*time.Second); err != nil {
|
||||
return fmt.Errorf("server did not become ready: %w", err)
|
||||
}
|
||||
|
||||
t.Logf(" ✓ Woodpecker server started successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Simulate user login
|
||||
func (s *TestServer) Login(code, state string) (string, error) {
|
||||
client := http.Client{Timeout: 10 * time.Second}
|
||||
resp, err := client.Get(fmt.Sprintf("http://localhost:8000/authorize?code=%s&state=%s", code, state))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to perform login request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("login request failed with status: %s", resp.Status)
|
||||
}
|
||||
|
||||
cookies := resp.Cookies()
|
||||
for _, cookie := range cookies {
|
||||
if cookie.Name == "user_sess" {
|
||||
return cookie.Value, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("user_sess cookie not found in login response")
|
||||
}
|
||||
|
||||
func (s *TestServer) Stop() error {
|
||||
if s.service == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := s.service.Stop(); err != nil {
|
||||
return fmt.Errorf("failed to stop server: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
183
test/integration/flows/pipeline/cancel_pipeline_test.go
Normal file
183
test/integration/flows/pipeline/cancel_pipeline_test.go
Normal file
@@ -0,0 +1,183 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pipeline_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/test/integration/env"
|
||||
)
|
||||
|
||||
// TestFlow_CancelPipeline tests the flow of canceling a running pipeline.
|
||||
//
|
||||
// Flow:
|
||||
// 1. Setup test environment
|
||||
// 2. Create a repository with a long-running pipeline
|
||||
// 3. Trigger the pipeline
|
||||
// 4. Wait for pipeline to start executing
|
||||
// 5. Cancel the pipeline via API
|
||||
// 6. Verify that the pipeline status changes to "killed" or "cancelled"
|
||||
// 7. Verify that running steps are stopped
|
||||
// 8. Verify that no new steps are started after cancellation
|
||||
func TestFlow_CancelPipeline(t *testing.T) {
|
||||
// Setup the complete test environment
|
||||
e := env.SetupTestEnv(t)
|
||||
e.Start()
|
||||
|
||||
// Define a pipeline with long-running steps
|
||||
// Using the mock backend, we can control step duration with SLEEP env var
|
||||
// pipelineConfig := `
|
||||
// when:
|
||||
// - event: push
|
||||
|
||||
// steps:
|
||||
// - name: long-running-step
|
||||
// image: alpine:latest
|
||||
// commands:
|
||||
// - echo "Starting long-running step"
|
||||
// - sleep 30 # This will be simulated by mock backend
|
||||
// - echo "This should not be reached if cancelled"
|
||||
|
||||
// - name: second-step
|
||||
// image: alpine:latest
|
||||
// commands:
|
||||
// - echo "This step should not start if pipeline is cancelled"
|
||||
// `
|
||||
|
||||
// TODO: Step 1: Create repository and push pipeline config
|
||||
t.Log("📝 Setting up repository with long-running pipeline...")
|
||||
// repo, err := env.CreateTestRepository("test-cancel-pipeline", pipelineConfig)
|
||||
// if err != nil {
|
||||
// t.Fatalf("Failed to create test repository: %v", err)
|
||||
// }
|
||||
|
||||
// TODO: Step 2: Activate repository
|
||||
t.Log("🔗 Activating repository...")
|
||||
// err = env.WoodpeckerClient.ActivateRepo(repo.Owner, repo.Name)
|
||||
|
||||
// TODO: Step 3: Trigger pipeline
|
||||
t.Log("🚀 Triggering pipeline...")
|
||||
// pipeline, err := env.WoodpeckerClient.TriggerPipeline(repo.Owner, repo.Name, "main")
|
||||
// if err != nil {
|
||||
// t.Fatalf("Failed to trigger pipeline: %v", err)
|
||||
// }
|
||||
// pipelineID := int(pipeline["number"].(float64))
|
||||
// t.Logf("✓ Pipeline #%d triggered", pipelineID)
|
||||
|
||||
// TODO: Step 4: Wait for pipeline to start running
|
||||
t.Log("⏳ Waiting for pipeline to start...")
|
||||
// var pipelineStatus string
|
||||
// for i := 0; i < 20; i++ {
|
||||
// p, err := env.WoodpeckerClient.GetPipeline(repo.Owner, repo.Name, pipelineID)
|
||||
// if err == nil {
|
||||
// pipelineStatus = p["status"].(string)
|
||||
// if pipelineStatus == "running" {
|
||||
// t.Log("✓ Pipeline is now running")
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// time.Sleep(500 * time.Millisecond)
|
||||
// }
|
||||
//
|
||||
// if pipelineStatus != "running" {
|
||||
// t.Fatalf("Pipeline did not start running, status: %s", pipelineStatus)
|
||||
// }
|
||||
|
||||
// TODO: Step 5: Cancel the pipeline
|
||||
t.Log("🛑 Cancelling pipeline...")
|
||||
// Give it a moment to ensure it's really running
|
||||
time.Sleep(2 * time.Second)
|
||||
// err = env.WoodpeckerClient.CancelPipeline(repo.Owner, repo.Name, pipelineID)
|
||||
// if err != nil {
|
||||
// t.Fatalf("Failed to cancel pipeline: %v", err)
|
||||
// }
|
||||
// t.Log("✓ Cancel request sent")
|
||||
|
||||
// TODO: Step 6: Wait for pipeline to be stopped
|
||||
t.Log("⏳ Waiting for pipeline to be cancelled...")
|
||||
// var finalStatus string
|
||||
// for i := 0; i < 20; i++ {
|
||||
// p, err := env.WoodpeckerClient.GetPipeline(repo.Owner, repo.Name, pipelineID)
|
||||
// if err == nil {
|
||||
// finalStatus = p["status"].(string)
|
||||
// if finalStatus == "killed" || finalStatus == "cancelled" {
|
||||
// t.Logf("✓ Pipeline status: %s", finalStatus)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// time.Sleep(500 * time.Millisecond)
|
||||
// }
|
||||
|
||||
// TODO: Step 7: Verify pipeline status
|
||||
t.Log("✅ Verifying pipeline was cancelled...")
|
||||
// if finalStatus != "killed" && finalStatus != "cancelled" {
|
||||
// t.Errorf("Expected pipeline status to be 'killed' or 'cancelled', got: %s", finalStatus)
|
||||
// }
|
||||
|
||||
// TODO: Step 8: Verify steps were stopped
|
||||
t.Log("📋 Verifying steps were stopped...")
|
||||
// steps, err := env.WoodpeckerClient.GetPipelineSteps(repo.Owner, repo.Name, pipelineID)
|
||||
// if err != nil {
|
||||
// t.Fatalf("Failed to get pipeline steps: %v", err)
|
||||
// }
|
||||
//
|
||||
// Verify that:
|
||||
// - First step was killed/cancelled
|
||||
// - Second step never started or was skipped
|
||||
// for _, step := range steps {
|
||||
// stepName := step["name"].(string)
|
||||
// stepStatus := step["status"].(string)
|
||||
// t.Logf(" Step '%s': %s", stepName, stepStatus)
|
||||
// }
|
||||
|
||||
t.Log("✅ Cancel pipeline flow test completed!")
|
||||
t.Log("")
|
||||
t.Log("ℹ️ This test verifies that:")
|
||||
t.Log(" - Running pipelines can be cancelled via API")
|
||||
t.Log(" - Pipeline status is updated to 'killed' or 'cancelled'")
|
||||
t.Log(" - Running steps are gracefully stopped")
|
||||
t.Log(" - Pending steps are not started after cancellation")
|
||||
t.Log(" - Agent properly handles cancellation signals")
|
||||
}
|
||||
|
||||
// TestFlow_CancelPipeline_MultipleSteps tests cancellation with multiple running steps
|
||||
func TestFlow_CancelPipeline_MultipleSteps(t *testing.T) {
|
||||
t.Skip("TODO: Implement test for cancelling with multiple concurrent steps")
|
||||
|
||||
// This test should verify:
|
||||
// - Pipeline with parallel steps can be cancelled
|
||||
// - All running steps are stopped
|
||||
// - No new steps start after cancellation
|
||||
}
|
||||
|
||||
// TestFlow_CancelPipeline_EarlyStage tests cancellation during early pipeline stages
|
||||
func TestFlow_CancelPipeline_EarlyStage(t *testing.T) {
|
||||
t.Skip("TODO: Implement test for early-stage cancellation")
|
||||
|
||||
// This test should verify:
|
||||
// - Pipeline can be cancelled during setup phase
|
||||
// - Pipeline can be cancelled before first step starts
|
||||
// - Resources are properly cleaned up even with early cancellation
|
||||
}
|
||||
|
||||
// TestFlow_CancelPipeline_AlreadyCompleted tests attempting to cancel a completed pipeline
|
||||
func TestFlow_CancelPipeline_AlreadyCompleted(t *testing.T) {
|
||||
t.Skip("TODO: Implement test for cancelling completed pipeline")
|
||||
|
||||
// This test should verify:
|
||||
// - Attempting to cancel a completed pipeline returns appropriate error/status
|
||||
// - Pipeline status remains "success" or "failure" (doesn't change to cancelled)
|
||||
}
|
||||
177
test/integration/flows/pipeline/manual_trigger_test.go
Normal file
177
test/integration/flows/pipeline/manual_trigger_test.go
Normal file
@@ -0,0 +1,177 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pipeline_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/test/integration/blocks"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/test/integration/env"
|
||||
)
|
||||
|
||||
// TestFlow_ManualTrigger tests the flow of manually triggering a pipeline via API.
|
||||
//
|
||||
// Flow:
|
||||
// 1. Setup test environment
|
||||
// 2. Create a repository with a pipeline configuration
|
||||
// 3. Activate the repository
|
||||
// 4. Manually trigger a pipeline via Woodpecker API (not via webhook)
|
||||
// 5. Verify that the pipeline is created and queued
|
||||
// 6. Verify that the pipeline executes successfully
|
||||
// 7. Verify pipeline metadata shows it was manually triggered
|
||||
func TestFlow_ManualTrigger(t *testing.T) {
|
||||
// Setup the complete test environment
|
||||
e := env.SetupTestEnv(t)
|
||||
e.Start()
|
||||
|
||||
// Define a simple pipeline for manual triggering
|
||||
pipelineConfig := `
|
||||
when:
|
||||
- event: manual
|
||||
|
||||
steps:
|
||||
- name: manual-pipeline-step
|
||||
image: alpine:latest
|
||||
commands:
|
||||
- echo "This pipeline was manually triggered!"
|
||||
- echo "No webhook was needed"
|
||||
- echo "Triggered via API call"
|
||||
`
|
||||
|
||||
// TODO: Step 1: Create repository
|
||||
t.Log("📝 Creating test repository...")
|
||||
|
||||
gitRepo := blocks.NewGitRepo(t)
|
||||
// gitRepo.Init(t)
|
||||
gitRepo.WriteFile(t, ".woodpecker.yml", []byte(pipelineConfig))
|
||||
gitRepo.Add(t, ".woodpecker.yml")
|
||||
gitRepo.Commit(t, ":tada: init")
|
||||
gitRepo.Push(t)
|
||||
t.Log("✓ Repository created and pipeline config pushed")
|
||||
|
||||
// TODO: Step 3: Activate repository in Woodpecker
|
||||
t.Log("🔗 Activating repository in Woodpecker...")
|
||||
// err = env.WoodpeckerClient.ActivateRepo(repo.Owner, repo.Name)
|
||||
// if err != nil {
|
||||
// t.Fatalf("Failed to activate repository: %v", err)
|
||||
// }
|
||||
repo := blocks.NewTestRepo()
|
||||
repo.Enable(t)
|
||||
t.Log("✓ Repository activated")
|
||||
|
||||
// TODO: Step 4: Manually trigger a pipeline
|
||||
t.Log("🚀 Manually triggering pipeline...")
|
||||
// Trigger with specific branch
|
||||
// branch := "main"
|
||||
// pipeline, err := env.WoodpeckerClient.TriggerPipeline(repo.Owner, repo.Name, branch)
|
||||
// if err != nil {
|
||||
// t.Fatalf("Failed to trigger pipeline: %v", err)
|
||||
// }
|
||||
// pipelineID := int(pipeline["number"].(float64))
|
||||
// t.Logf("✓ Manual pipeline #%d triggered", pipelineID)
|
||||
|
||||
// TODO: Step 5: Wait for pipeline to complete
|
||||
t.Log("⏳ Waiting for pipeline to complete...")
|
||||
// status, err := env.WoodpeckerClient.WaitForPipelineComplete(
|
||||
// repo.Owner,
|
||||
// repo.Name,
|
||||
// pipelineID,
|
||||
// 60*time.Second,
|
||||
// )
|
||||
// if err != nil {
|
||||
// t.Fatalf("Error waiting for pipeline: %v", err)
|
||||
// }
|
||||
|
||||
// TODO: Step 6: Verify pipeline succeeded
|
||||
t.Log("✅ Verifying pipeline status...")
|
||||
// if status != "success" {
|
||||
// t.Fatalf("Expected pipeline to succeed, got status: %s", status)
|
||||
// }
|
||||
// t.Log("✓ Pipeline completed successfully")
|
||||
|
||||
// TODO: Step 7: Verify pipeline metadata
|
||||
t.Log("📋 Verifying pipeline metadata...")
|
||||
// p, err := env.WoodpeckerClient.GetPipeline(repo.Owner, repo.Name, pipelineID)
|
||||
// if err != nil {
|
||||
// t.Fatalf("Failed to get pipeline details: %v", err)
|
||||
// }
|
||||
//
|
||||
// Verify event type is "manual" or "deploy"
|
||||
// event := p["event"].(string)
|
||||
// if event != "manual" && event != "deploy" {
|
||||
// t.Errorf("Expected event to be 'manual' or 'deploy', got: %s", event)
|
||||
// }
|
||||
//
|
||||
// Verify the branch
|
||||
// pipelineBranch := p["branch"].(string)
|
||||
// if pipelineBranch != branch {
|
||||
// t.Errorf("Expected branch to be '%s', got: %s", branch, pipelineBranch)
|
||||
// }
|
||||
|
||||
// TODO: Step 8: Verify logs
|
||||
t.Log("📋 Verifying pipeline logs...")
|
||||
// logs, err := env.WoodpeckerClient.GetPipelineLogs(repo.Owner, repo.Name, pipelineID)
|
||||
// if !strings.Contains(logs, "manually triggered") {
|
||||
// t.Error("Expected 'manually triggered' in logs")
|
||||
// }
|
||||
|
||||
t.Log("✅ Manual trigger flow test completed successfully!")
|
||||
t.Log("")
|
||||
t.Log("ℹ️ This test verifies that:")
|
||||
t.Log(" - Pipelines can be triggered manually via API")
|
||||
t.Log(" - No webhook or forge event is required")
|
||||
t.Log(" - Pipeline executes with correct branch/event metadata")
|
||||
t.Log(" - Manual triggers work independently of push events")
|
||||
}
|
||||
|
||||
// TestFlow_ManualTrigger_WithVariables tests manual trigger with custom variables
|
||||
func TestFlow_ManualTrigger_WithVariables(t *testing.T) {
|
||||
t.Skip("TODO: Implement test for manual trigger with variables")
|
||||
|
||||
// This test should verify:
|
||||
// - Manual triggers can include custom environment variables
|
||||
// - Variables are properly passed to pipeline steps
|
||||
// - Variables override defaults when specified
|
||||
}
|
||||
|
||||
// TestFlow_ManualTrigger_DifferentBranches tests manual triggering on different branches
|
||||
func TestFlow_ManualTrigger_DifferentBranches(t *testing.T) {
|
||||
t.Skip("TODO: Implement test for manual trigger on different branches")
|
||||
|
||||
// This test should verify:
|
||||
// - Manual trigger works on non-default branches
|
||||
// - Correct pipeline configuration is used for the specified branch
|
||||
// - Branch-specific when conditions are evaluated correctly
|
||||
}
|
||||
|
||||
// TestFlow_ManualTrigger_WhilePipelineRunning tests manual trigger while another is running
|
||||
func TestFlow_ManualTrigger_WhilePipelineRunning(t *testing.T) {
|
||||
t.Skip("TODO: Implement test for concurrent manual triggers")
|
||||
|
||||
// This test should verify:
|
||||
// - Multiple manual triggers can be queued
|
||||
// - Each pipeline runs independently
|
||||
// - Queue and concurrency limits are respected
|
||||
}
|
||||
|
||||
// TestFlow_ManualTrigger_WithParameters tests manual trigger with pipeline parameters
|
||||
func TestFlow_ManualTrigger_WithParameters(t *testing.T) {
|
||||
t.Skip("TODO: Implement test for manual trigger with parameters")
|
||||
|
||||
// This test should verify:
|
||||
// - Manual triggers can pass parameters to workflows
|
||||
// - Parameters are accessible in pipeline configuration
|
||||
// - Parameter validation works correctly
|
||||
}
|
||||
98
test/integration/flows/pipeline/push_trigger_test.go
Normal file
98
test/integration/flows/pipeline/push_trigger_test.go
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pipeline_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/test/integration/blocks"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/test/integration/env"
|
||||
)
|
||||
|
||||
func TestFlow_TriggerPipelineByPush(t *testing.T) {
|
||||
// Setup the complete test environment
|
||||
e := env.SetupTestEnv(t)
|
||||
e.Start()
|
||||
|
||||
// Define a simple pipeline configuration
|
||||
pipelineConfig := `
|
||||
when:
|
||||
- event: push
|
||||
|
||||
steps:
|
||||
- name: greeting
|
||||
image: alpine:latest
|
||||
commands:
|
||||
- echo "Hello from Woodpecker CI!"
|
||||
- echo "This pipeline was triggered by a push event"
|
||||
`
|
||||
|
||||
t.Log("📝 Creating git repository ...")
|
||||
gitRepo := blocks.NewGitRepo(t)
|
||||
cloneURL, err := e.Forge.GetRepositoryCloneURL(t.Name())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get repository clone URL: %v", err)
|
||||
}
|
||||
gitRepo.Init(t, cloneURL)
|
||||
gitRepo.WriteFile(t, "README.md", []byte(t.Name()))
|
||||
gitRepo.Add(t, "README.md")
|
||||
gitRepo.Commit(t, ":tada: initial commit")
|
||||
gitRepo.Push(t)
|
||||
|
||||
t.Log("🔗 Activating repository in Woodpecker...")
|
||||
|
||||
t.Log("🚀 Pushing pipeline config to trigger pipeline...")
|
||||
gitRepo.WriteFile(t, ".woodpecker.yml", []byte(pipelineConfig))
|
||||
gitRepo.Add(t, ".woodpecker.yml")
|
||||
gitRepo.Commit(t, ":tada: init")
|
||||
t.Log("✓ Pipeline config committed, pushing to trigger pipeline...")
|
||||
|
||||
// TODO: Step 5: Wait for pipeline to be created
|
||||
t.Log("⏳ Waiting for pipeline to be created...")
|
||||
// Poll Woodpecker API for pipeline
|
||||
// var pipelineID int
|
||||
// for i := 0; i < 10; i++ {
|
||||
// pipelines, err := env.WoodpeckerClient.GetPipelines(repo.Owner, repo.Name)
|
||||
// if err == nil && len(pipelines) > 0 {
|
||||
// pipelineID = pipelines[0]["number"].(int)
|
||||
// break
|
||||
// }
|
||||
// time.Sleep(1 * time.Second)
|
||||
// }
|
||||
|
||||
// TODO: Step 6: Wait for pipeline to complete
|
||||
t.Log("⏳ Waiting for pipeline to complete...")
|
||||
// status, err := env.WoodpeckerClient.WaitForPipelineComplete(
|
||||
// repo.Owner,
|
||||
// repo.Name,
|
||||
// pipelineID,
|
||||
// 60*time.Second,
|
||||
// )
|
||||
|
||||
// TODO: Step 7: Verify pipeline succeeded
|
||||
t.Log("✅ Verifying pipeline status...")
|
||||
// if status != "success" {
|
||||
// t.Fatalf("Expected pipeline to succeed, got status: %s", status)
|
||||
// }
|
||||
|
||||
// TODO: Step 8: Verify logs contain expected output
|
||||
t.Log("📋 Verifying pipeline logs...")
|
||||
// logs, err := env.WoodpeckerClient.GetPipelineLogs(repo.Owner, repo.Name, pipelineID)
|
||||
// if !strings.Contains(logs, "Hello from Woodpecker CI!") {
|
||||
// t.Error("Expected log output not found")
|
||||
// }
|
||||
|
||||
t.Log("✅ Push trigger flow test completed successfully!")
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package integration
|
||||
package repo_test
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestRegistryInjected(t *testing.T) {
|
||||
func TestFlow_RegistryInjected(t *testing.T) {
|
||||
// TODO: check if a registry was injected into the pipeline config
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package integration
|
||||
package repo_test
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSecretInjected(t *testing.T) {
|
||||
func TestFlow_SecretInjected(t *testing.T) {
|
||||
// TODO: check if a secret was injected into the pipeline config
|
||||
}
|
||||
38
test/integration/flows/repo/repo_test.go
Normal file
38
test/integration/flows/repo/repo_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package repo_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/test/integration/blocks"
|
||||
)
|
||||
|
||||
func TestFlow_EnableRepo(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
repo := blocks.NewTestRepo()
|
||||
repo.Enable(t)
|
||||
}
|
||||
|
||||
func TestFlow_RepairRepo(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
repo := blocks.NewTestRepo()
|
||||
repo.Enable(t)
|
||||
repo.Repair(t)
|
||||
}
|
||||
|
||||
func TestFlow_DisableRepo(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
repo := blocks.NewTestRepo()
|
||||
repo.Enable(t)
|
||||
repo.Disable(t)
|
||||
}
|
||||
|
||||
func TestFlow_DeleteRepo(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
repo := blocks.NewTestRepo()
|
||||
repo.Enable(t)
|
||||
repo.Delete(t)
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/test/integration/utils"
|
||||
)
|
||||
|
||||
// TestSimplePipelineExecution demonstrates the full integration test workflow
|
||||
// This test verifies that a basic pipeline can execute successfully through all components
|
||||
func TestSimplePipelineExecution(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
// Step 1: Start Woodpecker server
|
||||
server, err := utils.StartServer(t)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not start server: %s", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := server.Stop(); err != nil {
|
||||
t.Logf("Warning: failed to stop server: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Step 2: Start Woodpecker agent
|
||||
agent, err := utils.StartAgent(t)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not start agent: %s", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := agent.Stop(); err != nil {
|
||||
t.Logf("Warning: failed to stop agent: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Give the system a moment to stabilize
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
t.Log("✓ Server and agent started successfully")
|
||||
|
||||
// Step 3: Create API client (in production, you'd authenticate with the forge)
|
||||
// For now, we're using the admin user in OPEN mode
|
||||
client := utils.NewWoodpeckerClient("http://localhost:8000", "")
|
||||
|
||||
// Step 4: Create a test repository with a simple pipeline
|
||||
config := utils.TestRepoConfig{
|
||||
Name: "test-repo",
|
||||
PipelineConfig: utils.SimplePipelineConfig(),
|
||||
}
|
||||
repoPath := utils.CreateTestRepo(t, config)
|
||||
t.Logf("✓ Test repository created at: %s", repoPath)
|
||||
|
||||
// TODO: The following steps require forge integration
|
||||
// Once Gitea is running, these would work:
|
||||
//
|
||||
// 5. Push repository to Gitea
|
||||
// 6. Activate repository in Woodpecker via API
|
||||
// 7. Trigger a pipeline (webhook or manual)
|
||||
// 8. Wait for pipeline to complete
|
||||
// 9. Verify pipeline succeeded
|
||||
// 10. Check pipeline logs
|
||||
|
||||
// For now, just verify the API is accessible
|
||||
_, err = client.GetRepos()
|
||||
if err != nil {
|
||||
t.Logf("Note: Could not fetch repos (expected without forge): %v", err)
|
||||
}
|
||||
|
||||
t.Log("✓ Integration test framework is ready!")
|
||||
t.Log("Next: Add forge (Gitea) integration to complete the workflow")
|
||||
}
|
||||
|
||||
// TestAgentConnection verifies that the agent can connect to the server
|
||||
func TestAgentConnection(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
// Start server
|
||||
server, err := utils.StartServer(t)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not start server: %s", err)
|
||||
}
|
||||
defer server.Stop()
|
||||
|
||||
// Start agent
|
||||
agent, err := utils.StartAgent(t)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not start agent: %s", err)
|
||||
}
|
||||
defer agent.Stop()
|
||||
|
||||
// Wait for connection to establish
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
// TODO: Add API endpoint check to verify agent is connected
|
||||
// This could be done via the server's API: GET /api/agents
|
||||
|
||||
t.Log("✓ Agent connection test completed")
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/test/integration/utils"
|
||||
)
|
||||
|
||||
// TestEnvStart verifies that all components can start successfully
|
||||
func TestEnvStart(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
// Start server
|
||||
server, err := utils.StartServer(t)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not start server: %s", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := server.Stop(); err != nil {
|
||||
t.Logf("Warning: failed to stop server: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Start agent
|
||||
agent, err := utils.StartAgent(t)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not start agent: %s", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := agent.Stop(); err != nil {
|
||||
t.Logf("Warning: failed to stop agent: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for services to stabilize
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
// Verify server health endpoint
|
||||
if err := utils.WaitForHTTP("http://localhost:8000/healthz", 5*time.Second); err != nil {
|
||||
t.Fatalf("Server health check failed: %v", err)
|
||||
}
|
||||
|
||||
t.Log("✓ All components started successfully")
|
||||
t.Log("✓ Server is healthy and responding")
|
||||
t.Log("✓ Agent is running")
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package pipeline_test
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestPipelineByPush(t *testing.T) {
|
||||
// TODO: push to forge
|
||||
// TODO: check pipeline list
|
||||
// TODO: check if pipeline is push
|
||||
// TODO: check for correct branch, commit message, ...
|
||||
}
|
||||
|
||||
func TestPipelineByTag(t *testing.T) {
|
||||
// TODO: push tag to forge
|
||||
// TODO: check pipeline list
|
||||
// TODO: check if pipeline is tag
|
||||
// TODO: check for correct tag, ...
|
||||
}
|
||||
|
||||
func TestPipelineByRelease(t *testing.T) {
|
||||
// TODO: push tag to forge
|
||||
// TODO: check pipeline list
|
||||
// TODO: check if pipeline is release
|
||||
// TODO: check for correct tag, ...
|
||||
}
|
||||
|
||||
func TestPipelineByManual(t *testing.T) {
|
||||
// TODO: push tag to forge
|
||||
// TODO: check pipeline list
|
||||
// TODO: check if pipeline is manual
|
||||
}
|
||||
|
||||
func TestPipelineExecuted(t *testing.T) {
|
||||
// TODO: push to forge
|
||||
// TODO: check pipeline list
|
||||
// TODO: start agent
|
||||
// TODO: check logs and thereby if agent worked successfully
|
||||
}
|
||||
|
||||
func TestRestartPipeline(t *testing.T) {
|
||||
// TODO: push to forge
|
||||
// TODO: check pipeline list
|
||||
// TODO: start agent
|
||||
// TODO: check pipeline finished
|
||||
// TODO: restart pipeline
|
||||
// TODO: wait for restarted pipeline to finish
|
||||
}
|
||||
|
||||
func TestApprovePipeline(t *testing.T) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func TestDeclinePipeline(t *testing.T) {
|
||||
// TODO
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/test/integration/utils"
|
||||
)
|
||||
|
||||
// TestBasicPipelineExecution tests that a simple pipeline can be executed end-to-end
|
||||
// This is the foundational integration test that verifies:
|
||||
// 1. Server starts and is accessible
|
||||
// 2. Agent connects to server
|
||||
// 3. A simple "hello world" pipeline can be triggered and executed
|
||||
func TestBasicPipelineExecution(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Start the woodpecker server
|
||||
server, err := utils.StartServer(t)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not start server: %s", err)
|
||||
}
|
||||
defer server.Stop()
|
||||
|
||||
// Start the woodpecker agent
|
||||
agent, err := utils.StartAgent(t)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not start agent: %s", err)
|
||||
}
|
||||
defer agent.Stop()
|
||||
|
||||
// TODO: check server api if agent is connected
|
||||
|
||||
// TODO: Once forge integration is working:
|
||||
// 1. Create a test repository with a simple .woodpecker.yml
|
||||
// 2. Register the repository with Woodpecker
|
||||
// 3. Trigger a pipeline (e.g., via webhook or manual trigger)
|
||||
// 4. Wait for pipeline to complete
|
||||
// 5. Verify pipeline succeeded
|
||||
|
||||
t.Log("✓ Server and agent started successfully")
|
||||
t.Log("Next steps: Add repository creation, pipeline trigger, and result verification")
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package integration
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEnableRepo(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestRepairRepo(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestDisableRepo(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestDeleteRepo(t *testing.T) {
|
||||
|
||||
}
|
||||
@@ -28,7 +28,7 @@ func NewWoodpeckerClient(baseURL, token string) *WoodpeckerClient {
|
||||
}
|
||||
|
||||
// doRequest performs an HTTP request with authentication
|
||||
func (c *WoodpeckerClient) doRequest(method, path string, body interface{}) (*http.Response, error) {
|
||||
func (c *WoodpeckerClient) doRequest(method, path string, body any) (*http.Response, error) {
|
||||
var bodyReader io.Reader
|
||||
if body != nil {
|
||||
jsonData, err := json.Marshal(body)
|
||||
@@ -54,7 +54,7 @@ func (c *WoodpeckerClient) doRequest(method, path string, body interface{}) (*ht
|
||||
}
|
||||
|
||||
// GetRepos fetches the list of repositories
|
||||
func (c *WoodpeckerClient) GetRepos() ([]map[string]interface{}, error) {
|
||||
func (c *WoodpeckerClient) GetRepos() ([]map[string]any, error) {
|
||||
resp, err := c.doRequest("GET", "/api/repos", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -66,7 +66,7 @@ func (c *WoodpeckerClient) GetRepos() ([]map[string]interface{}, error) {
|
||||
return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var repos []map[string]interface{}
|
||||
var repos []map[string]any
|
||||
if err := json.NewDecoder(resp.Body).Decode(&repos); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
@@ -92,7 +92,7 @@ func (c *WoodpeckerClient) ActivateRepo(owner, name string) error {
|
||||
}
|
||||
|
||||
// GetPipeline fetches a specific pipeline
|
||||
func (c *WoodpeckerClient) GetPipeline(owner, name string, pipelineID int) (map[string]interface{}, error) {
|
||||
func (c *WoodpeckerClient) GetPipeline(owner, name string, pipelineID int) (map[string]any, error) {
|
||||
path := fmt.Sprintf("/api/repos/%s/%s/pipelines/%d", owner, name, pipelineID)
|
||||
resp, err := c.doRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
@@ -105,7 +105,7 @@ func (c *WoodpeckerClient) GetPipeline(owner, name string, pipelineID int) (map[
|
||||
return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var pipeline map[string]interface{}
|
||||
var pipeline map[string]any
|
||||
if err := json.NewDecoder(resp.Body).Decode(&pipeline); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
@@ -114,7 +114,7 @@ func (c *WoodpeckerClient) GetPipeline(owner, name string, pipelineID int) (map[
|
||||
}
|
||||
|
||||
// TriggerPipeline manually triggers a pipeline
|
||||
func (c *WoodpeckerClient) TriggerPipeline(owner, name, branch string) (map[string]interface{}, error) {
|
||||
func (c *WoodpeckerClient) TriggerPipeline(owner, name, branch string) (map[string]any, error) {
|
||||
path := fmt.Sprintf("/api/repos/%s/%s/pipelines", owner, name)
|
||||
body := map[string]string{
|
||||
"branch": branch,
|
||||
@@ -131,7 +131,7 @@ func (c *WoodpeckerClient) TriggerPipeline(owner, name, branch string) (map[stri
|
||||
return nil, fmt.Errorf("failed to trigger pipeline: status %d, body: %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
var pipeline map[string]interface{}
|
||||
var pipeline map[string]any
|
||||
if err := json.NewDecoder(resp.Body).Decode(&pipeline); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
@@ -139,6 +139,103 @@ func (c *WoodpeckerClient) TriggerPipeline(owner, name, branch string) (map[stri
|
||||
return pipeline, nil
|
||||
}
|
||||
|
||||
// CancelPipeline cancels a running pipeline
|
||||
func (c *WoodpeckerClient) CancelPipeline(owner, name string, pipelineID int) error {
|
||||
path := fmt.Sprintf("/api/repos/%s/%s/pipelines/%d/cancel", owner, name, pipelineID)
|
||||
resp, err := c.doRequest("POST", path, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("failed to cancel pipeline: status %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPipelines fetches all pipelines for a repository
|
||||
func (c *WoodpeckerClient) GetPipelines(owner, name string) ([]map[string]any, error) {
|
||||
path := fmt.Sprintf("/api/repos/%s/%s/pipelines", owner, name)
|
||||
resp, err := c.doRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var pipelines []map[string]any
|
||||
if err := json.NewDecoder(resp.Body).Decode(&pipelines); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
return pipelines, nil
|
||||
}
|
||||
|
||||
// GetPipelineSteps fetches all steps for a pipeline
|
||||
func (c *WoodpeckerClient) GetPipelineSteps(owner, name string, pipelineID int) ([]map[string]any, error) {
|
||||
path := fmt.Sprintf("/api/repos/%s/%s/pipelines/%d", owner, name, pipelineID)
|
||||
resp, err := c.doRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var pipeline map[string]any
|
||||
if err := json.NewDecoder(resp.Body).Decode(&pipeline); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
// Extract steps/workflows from pipeline
|
||||
// TODO: Adjust based on actual API response structure
|
||||
steps, ok := pipeline["steps"].([]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("steps not found in pipeline response")
|
||||
}
|
||||
|
||||
result := make([]map[string]any, len(steps))
|
||||
for i, step := range steps {
|
||||
result[i] = step.(map[string]any)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetPipelineLogs fetches logs for a pipeline
|
||||
func (c *WoodpeckerClient) GetPipelineLogs(owner, name string, pipelineID int) (string, error) {
|
||||
// TODO: Implement based on actual Woodpecker API
|
||||
// This might require iterating through workflow steps and fetching logs for each
|
||||
path := fmt.Sprintf("/api/repos/%s/%s/pipelines/%d/logs", owner, name, pipelineID)
|
||||
resp, err := c.doRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return "", fmt.Errorf("failed to get logs: status %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
logs, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read logs: %w", err)
|
||||
}
|
||||
|
||||
return string(logs), nil
|
||||
}
|
||||
|
||||
// WaitForPipelineComplete waits for a pipeline to complete (success or failure)
|
||||
func (c *WoodpeckerClient) WaitForPipelineComplete(owner, name string, pipelineID int, timeout time.Duration) (string, error) {
|
||||
deadline := time.Now().Add(timeout)
|
||||
|
||||
@@ -13,10 +13,11 @@ type Command struct {
|
||||
env map[string]string
|
||||
}
|
||||
|
||||
func NewTask(cmdName string, args ...string) *Command {
|
||||
func NewCommand(cmdName string, args ...string) *Command {
|
||||
cmd := exec.Command(cmdName, args...)
|
||||
return &Command{
|
||||
cmd: cmd,
|
||||
env: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
package utils
|
||||
|
||||
import "testing"
|
||||
|
||||
type TestRepo struct {
|
||||
folder string
|
||||
}
|
||||
|
||||
func (r *TestRepo) Clone(t *testing.T, sourcePath, remoteURL string) error {
|
||||
r.folder = t.TempDir()
|
||||
|
||||
NewTask("cp", "-r", sourcePath, r.folder).RunOrFail(t)
|
||||
NewTask("git", "init").RunOrFail(t)
|
||||
NewTask("git", "remote", "add", remoteURL).RunOrFail(t)
|
||||
|
||||
r.Commit(t, ":tada: init")
|
||||
|
||||
r.Push(t)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *TestRepo) Commit(t *testing.T, message string) {
|
||||
NewTask("git", "commit", "-m", message).RunOrFail(t)
|
||||
}
|
||||
|
||||
func (r *TestRepo) Push(t *testing.T) {
|
||||
NewTask("git", "push", "-u", "origin", "main").RunOrFail(t)
|
||||
}
|
||||
|
||||
func (r *TestRepo) Tag(t *testing.T, name, message string) {
|
||||
NewTask("git", "tag", "-a", name, "-m", message).RunOrFail(t)
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestRepoConfig holds configuration for creating a test repository
|
||||
type TestRepoConfig struct {
|
||||
Name string
|
||||
PipelineConfig string
|
||||
}
|
||||
|
||||
// CreateTestRepo creates a test repository with a Woodpecker pipeline configuration
|
||||
func CreateTestRepo(t *testing.T, config TestRepoConfig) string {
|
||||
// Create temporary directory for the repo
|
||||
repoDir := t.TempDir()
|
||||
|
||||
// Create .woodpecker.yml with the provided config
|
||||
woodpeckerYml := filepath.Join(repoDir, ".woodpecker.yml")
|
||||
if err := os.WriteFile(woodpeckerYml, []byte(config.PipelineConfig), 0644); err != nil {
|
||||
t.Fatalf("Failed to create .woodpecker.yml: %v", err)
|
||||
}
|
||||
|
||||
// Initialize git repository
|
||||
NewTask("git", "init").WorkDir(repoDir).RunOrFail(t)
|
||||
NewTask("git", "config", "user.name", "Test User").WorkDir(repoDir).RunOrFail(t)
|
||||
NewTask("git", "config", "user.email", "test@example.com").WorkDir(repoDir).RunOrFail(t)
|
||||
NewTask("git", "add", ".").WorkDir(repoDir).RunOrFail(t)
|
||||
NewTask("git", "commit", "-m", "Initial commit").WorkDir(repoDir).RunOrFail(t)
|
||||
|
||||
t.Logf("Created test repository at: %s", repoDir)
|
||||
return repoDir
|
||||
}
|
||||
|
||||
// SimplePipelineConfig returns a basic pipeline configuration for testing
|
||||
func SimplePipelineConfig() string {
|
||||
return `
|
||||
when:
|
||||
- event: push
|
||||
branch: main
|
||||
|
||||
steps:
|
||||
- name: greeting
|
||||
image: alpine:latest
|
||||
commands:
|
||||
- echo "Hello from Woodpecker!"
|
||||
- echo "Pipeline is working correctly"
|
||||
`
|
||||
}
|
||||
|
||||
// MultiStepPipelineConfig returns a pipeline with multiple steps
|
||||
func MultiStepPipelineConfig() string {
|
||||
return `
|
||||
when:
|
||||
- event: push
|
||||
branch: main
|
||||
|
||||
steps:
|
||||
- name: step1
|
||||
image: alpine:latest
|
||||
commands:
|
||||
- echo "Step 1: Starting"
|
||||
- sleep 1
|
||||
|
||||
- name: step2
|
||||
image: alpine:latest
|
||||
commands:
|
||||
- echo "Step 2: Running"
|
||||
- sleep 1
|
||||
|
||||
- name: step3
|
||||
image: alpine:latest
|
||||
commands:
|
||||
- echo "Step 3: Completed"
|
||||
`
|
||||
}
|
||||
|
||||
// FailingPipelineConfig returns a pipeline that will fail
|
||||
func FailingPipelineConfig() string {
|
||||
return `
|
||||
when:
|
||||
- event: push
|
||||
branch: main
|
||||
|
||||
steps:
|
||||
- name: will-fail
|
||||
image: alpine:latest
|
||||
commands:
|
||||
- echo "This step will fail"
|
||||
- exit 1
|
||||
`
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func StartForge(t *testing.T) (*Service, error) {
|
||||
// Start Gitea using docker-compose
|
||||
// Use the existing docker-compose file
|
||||
projectRoot := getProjectRoot()
|
||||
composeFile := filepath.Join(projectRoot, "data", "gitea", "docker-compose.yml")
|
||||
|
||||
service := NewService("docker-compose", "-f", composeFile, "up", "-d")
|
||||
|
||||
if err := service.Start(); err != nil {
|
||||
return nil, fmt.Errorf("failed to start forge: %w", err)
|
||||
}
|
||||
|
||||
// Wait for Gitea to be ready
|
||||
if err := WaitForHTTP("http://localhost:3000", 30*time.Second); err != nil {
|
||||
return nil, fmt.Errorf("forge did not become ready: %w", err)
|
||||
}
|
||||
|
||||
t.Logf("Forge (Gitea) started successfully")
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func StartServer(t *testing.T) (*Service, error) {
|
||||
projectRoot := getProjectRoot()
|
||||
|
||||
// Prepare web dist directory
|
||||
NewTask("mkdir", "-p", filepath.Join(projectRoot, "web/dist")).RunOrFail(t)
|
||||
NewTask("sh", "-c", fmt.Sprintf("echo test > %s", filepath.Join(projectRoot, "web/dist/index.html"))).RunOrFail(t)
|
||||
|
||||
service := NewService("go", "run", "./cmd/server/").
|
||||
WorkDir(projectRoot).
|
||||
// Server configuration
|
||||
SetEnv("WOODPECKER_OPEN", "true").
|
||||
SetEnv("WOODPECKER_ADMIN", "woodpecker").
|
||||
SetEnv("WOODPECKER_HOST", "http://localhost:8000").
|
||||
SetEnv("WOODPECKER_SERVER_ADDR", ":8000").
|
||||
SetEnv("WOODPECKER_GRPC_ADDR", ":9000").
|
||||
SetEnv("WOODPECKER_WEBHOOK_HOST", "http://localhost:8000").
|
||||
SetEnv("WOODPECKER_AGENT_SECRET", "test-secret-123").
|
||||
// Gitea forge configuration
|
||||
SetEnv("WOODPECKER_GITEA", "true").
|
||||
SetEnv("WOODPECKER_GITEA_URL", "http://localhost:3000").
|
||||
SetEnv("WOODPECKER_GITEA_CLIENT", "test-client").
|
||||
SetEnv("WOODPECKER_GITEA_SECRET", "test-secret").
|
||||
// Log level
|
||||
SetEnv("WOODPECKER_LOG_LEVEL", "debug")
|
||||
|
||||
if err := service.Start(); err != nil {
|
||||
return nil, fmt.Errorf("failed to start server: %w", err)
|
||||
}
|
||||
|
||||
// Wait for server to be ready
|
||||
if err := WaitForHTTP("http://localhost:8000/healthz", 30*time.Second); err != nil {
|
||||
return nil, fmt.Errorf("server did not become ready: %w", err)
|
||||
}
|
||||
|
||||
t.Logf("Woodpecker server started successfully")
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func StartAgent(t *testing.T) (*Service, error) {
|
||||
projectRoot := getProjectRoot()
|
||||
|
||||
service := NewService("go", "run", "./cmd/agent/").
|
||||
WorkDir(projectRoot).
|
||||
// Agent configuration
|
||||
SetEnv("WOODPECKER_SERVER", "localhost:9000").
|
||||
SetEnv("WOODPECKER_AGENT_SECRET", "test-secret-123").
|
||||
SetEnv("WOODPECKER_MAX_WORKFLOWS", "1").
|
||||
SetEnv("WOODPECKER_HEALTHCHECK", "false").
|
||||
SetEnv("WOODPECKER_BACKEND", "docker").
|
||||
// Log level
|
||||
SetEnv("WOODPECKER_LOG_LEVEL", "debug")
|
||||
|
||||
if err := service.Start(); err != nil {
|
||||
return nil, fmt.Errorf("failed to start agent: %w", err)
|
||||
}
|
||||
|
||||
// Give agent time to connect
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
t.Logf("Woodpecker agent started successfully")
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func getProjectRoot() string {
|
||||
// This assumes tests run from test/integration/
|
||||
return "../.."
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/test/integration/utils"
|
||||
)
|
||||
|
||||
// TestCompleteWorkflow demonstrates a full end-to-end workflow
|
||||
// NOTE: This test requires Gitea to be running and properly configured
|
||||
// Uncomment and use once forge integration is complete
|
||||
func TestCompleteWorkflow(t *testing.T) {
|
||||
t.Skip("Skipping until forge integration is complete")
|
||||
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
// Step 1: Start all services
|
||||
t.Log("Starting Gitea forge...")
|
||||
forge, err := utils.StartForge(t)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not start forge: %s", err)
|
||||
}
|
||||
defer forge.Stop()
|
||||
|
||||
t.Log("Starting Woodpecker server...")
|
||||
server, err := utils.StartServer(t)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not start server: %s", err)
|
||||
}
|
||||
defer server.Stop()
|
||||
|
||||
t.Log("Starting Woodpecker agent...")
|
||||
agent, err := utils.StartAgent(t)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not start agent: %s", err)
|
||||
}
|
||||
defer agent.Stop()
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
t.Log("✓ All services started")
|
||||
|
||||
// Step 2: Create and configure test repository
|
||||
config := utils.TestRepoConfig{
|
||||
Name: "test-pipeline-repo",
|
||||
PipelineConfig: utils.MultiStepPipelineConfig(),
|
||||
}
|
||||
repoPath := utils.CreateTestRepo(t, config)
|
||||
t.Logf("✓ Created test repository at: %s", repoPath)
|
||||
|
||||
// Step 3: Push to Gitea
|
||||
// TODO: Implement pushing to Gitea
|
||||
// This would involve:
|
||||
// - Creating a repository in Gitea via API
|
||||
// - Adding Gitea as a remote
|
||||
// - Pushing the repository
|
||||
t.Log("TODO: Push repository to Gitea")
|
||||
|
||||
// Step 4: Activate repository in Woodpecker
|
||||
client := utils.NewWoodpeckerClient("http://localhost:8000", "your-token-here")
|
||||
|
||||
owner := "woodpecker"
|
||||
repoName := "test-pipeline-repo"
|
||||
|
||||
err = client.ActivateRepo(owner, repoName)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to activate repository: %v", err)
|
||||
}
|
||||
t.Log("✓ Repository activated in Woodpecker")
|
||||
|
||||
// Step 5: Trigger a pipeline
|
||||
pipeline, err := client.TriggerPipeline(owner, repoName, "main")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to trigger pipeline: %v", err)
|
||||
}
|
||||
|
||||
pipelineID := int(pipeline["id"].(float64))
|
||||
t.Logf("✓ Pipeline #%d triggered", pipelineID)
|
||||
|
||||
// Step 6: Wait for pipeline to complete
|
||||
status, err := client.WaitForPipelineComplete(owner, repoName, pipelineID, 5*time.Minute)
|
||||
if err != nil {
|
||||
t.Fatalf("Pipeline did not complete: %v", err)
|
||||
}
|
||||
|
||||
// Step 7: Verify success
|
||||
if status != "success" {
|
||||
t.Fatalf("Pipeline failed with status: %s", status)
|
||||
}
|
||||
|
||||
t.Logf("✓ Pipeline completed successfully!")
|
||||
t.Log("✓ Complete workflow test passed!")
|
||||
}
|
||||
|
||||
// TestPipelineFailure tests that pipeline failures are handled correctly
|
||||
func TestPipelineFailure(t *testing.T) {
|
||||
t.Skip("Skipping until forge integration is complete")
|
||||
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
// Start services (abbreviated)
|
||||
server, _ := utils.StartServer(t)
|
||||
defer server.Stop()
|
||||
|
||||
agent, _ := utils.StartAgent(t)
|
||||
defer agent.Stop()
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
// Create a repository with a failing pipeline
|
||||
config := utils.TestRepoConfig{
|
||||
Name: "failing-pipeline",
|
||||
PipelineConfig: utils.FailingPipelineConfig(),
|
||||
}
|
||||
utils.CreateTestRepo(t, config)
|
||||
|
||||
// TODO: Complete the test once forge integration is ready
|
||||
// 1. Push to Gitea
|
||||
// 2. Activate repo
|
||||
// 3. Trigger pipeline
|
||||
// 4. Verify it fails with expected status
|
||||
// 5. Check error logs
|
||||
|
||||
t.Log("TODO: Complete pipeline failure test")
|
||||
}
|
||||
|
||||
// TestMultipleConcurrentPipelines tests handling of multiple pipelines
|
||||
func TestMultipleConcurrentPipelines(t *testing.T) {
|
||||
t.Skip("Skipping until basic workflow is stable")
|
||||
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
// This test would verify:
|
||||
// 1. Multiple repositories can be activated
|
||||
// 2. Pipelines from different repos can run concurrently
|
||||
// 3. Agent properly handles workflow queue
|
||||
// 4. Results are correctly associated with respective repos
|
||||
|
||||
t.Log("TODO: Implement concurrent pipeline test")
|
||||
}
|
||||
Reference in New Issue
Block a user