mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2026-05-15 04:08:14 +00:00
215 lines
7.1 KiB
Go
215 lines
7.1 KiB
Go
// 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 setup
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/urfave/cli/v3"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/keepalive"
|
|
|
|
"go.woodpecker-ci.org/woodpecker/v3/rpc/proto"
|
|
"go.woodpecker-ci.org/woodpecker/v3/server"
|
|
"go.woodpecker-ci.org/woodpecker/v3/server/cache"
|
|
"go.woodpecker-ci.org/woodpecker/v3/server/forge"
|
|
forge_mocks "go.woodpecker-ci.org/woodpecker/v3/server/forge/mocks"
|
|
forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types"
|
|
"go.woodpecker-ci.org/woodpecker/v3/server/logging"
|
|
"go.woodpecker-ci.org/woodpecker/v3/server/model"
|
|
"go.woodpecker-ci.org/woodpecker/v3/server/pubsub/memory"
|
|
"go.woodpecker-ci.org/woodpecker/v3/server/queue"
|
|
server_rpc "go.woodpecker-ci.org/woodpecker/v3/server/rpc"
|
|
"go.woodpecker-ci.org/woodpecker/v3/server/scheduler"
|
|
"go.woodpecker-ci.org/woodpecker/v3/server/services"
|
|
"go.woodpecker-ci.org/woodpecker/v3/server/services/permissions"
|
|
"go.woodpecker-ci.org/woodpecker/v3/server/store"
|
|
)
|
|
|
|
const (
|
|
// TestAgentToken is the shared secret used between the in-process server
|
|
// and agent. Hard-coded for tests — not a real secret.
|
|
TestAgentToken = "test-agent-secret-for-integration-tests"
|
|
|
|
// TestJWTSecret is used for signing gRPC auth JWTs.
|
|
TestJWTSecret = "test-jwt-secret-for-integration-tests"
|
|
|
|
// TestForgeType is the forge type the mock pretends to bee.
|
|
TestForgeType = model.ForgeTypeGitea
|
|
)
|
|
|
|
var configLock = sync.Mutex{}
|
|
|
|
// ServerEnv holds all the pieces of a running test server environment.
|
|
type ServerEnv struct {
|
|
GRPCAddr string
|
|
Store store.Store
|
|
Queue queue.Queue
|
|
Fixtures *Fixtures
|
|
Forge *forge_mocks.MockForge
|
|
Manager services.Manager
|
|
}
|
|
|
|
// StartServer wires up the full in-process server stack:
|
|
// - in-memory sqlite store (fully migrated) with seeded fixtures
|
|
// - in-memory queue, pubsub, and logging
|
|
// - MockForge that serves the provided workflow files
|
|
// - gRPC server on a random TCP port
|
|
//
|
|
// files must contain at least one entry. Single-workflow scenarios pass one
|
|
// file named ".woodpecker.yaml"; multi-workflow scenarios pass multiple files
|
|
// named ".woodpecker/foo.yaml" etc. The repo's Config path is set accordingly.
|
|
//
|
|
// All resources are cleaned up via t.Cleanup.
|
|
func StartServer(ctx context.Context, t *testing.T, files []*forge_types.FileMeta) *ServerEnv {
|
|
t.Helper()
|
|
configLock.Lock()
|
|
defer configLock.Unlock()
|
|
|
|
memStore := newStore(ctx, t)
|
|
fixtures := seedFixtures(t, memStore)
|
|
mockForge := newMockForge(t, files)
|
|
|
|
mgr, err := newTestManager(memStore, mockForge)
|
|
require.NoError(t, err, "create services manager")
|
|
|
|
memQueue, err := queue.New(ctx, queue.Config{Backend: queue.TypeMemory})
|
|
require.NoError(t, err, "create queue")
|
|
|
|
// Save and restore server.Config around the test. server.Config is a
|
|
// package-level global read by server/pipeline and server/rpc. Tests run
|
|
// sequentially within a package, but we still need to clean up so the next
|
|
// subtest starts from a known-zero state rather than the previous test's values.
|
|
orig := server.Config
|
|
t.Cleanup(func() {
|
|
configLock.Lock()
|
|
defer configLock.Unlock()
|
|
server.Config = orig
|
|
})
|
|
|
|
server.Config.Services.Logs = logging.New()
|
|
server.Config.Services.Scheduler = scheduler.NewScheduler(memQueue, memory.New())
|
|
server.Config.Services.Membership = cache.NewMembershipService(memStore)
|
|
server.Config.Services.Manager = mgr
|
|
server.Config.Services.LogStore = memStore
|
|
|
|
server.Config.Server.AgentToken = TestAgentToken
|
|
server.Config.Server.Host = "http://localhost"
|
|
server.Config.Server.JWTSecret = TestJWTSecret
|
|
|
|
server.Config.Pipeline.DefaultClonePlugin = "docker.io/woodpeckerci/plugin-git:latest"
|
|
server.Config.Pipeline.TrustedClonePlugins = []string{"docker.io/woodpeckerci/plugin-git:latest"}
|
|
server.Config.Pipeline.DefaultApprovalMode = model.RequireApprovalNone
|
|
server.Config.Pipeline.DefaultTimeout = 60
|
|
server.Config.Pipeline.MaxTimeout = 60
|
|
|
|
server.Config.Permissions.Open = true
|
|
server.Config.Permissions.Admins = permissions.NewAdmins([]string{})
|
|
server.Config.Permissions.Orgs = permissions.NewOrgs([]string{})
|
|
server.Config.Permissions.OwnersAllowlist = permissions.NewOwnersAllowlist([]string{})
|
|
|
|
grpcAddr := startGRPCServer(ctx, t, memStore)
|
|
|
|
return &ServerEnv{
|
|
GRPCAddr: grpcAddr,
|
|
Store: memStore,
|
|
Queue: memQueue,
|
|
Fixtures: fixtures,
|
|
Forge: mockForge,
|
|
Manager: mgr,
|
|
}
|
|
}
|
|
|
|
// newTestManager builds a services.Manager whose SetupForge always returns
|
|
// the provided MockForge, bypassing real forge instantiation.
|
|
func newTestManager(s store.Store, mockForge *forge_mocks.MockForge) (services.Manager, error) {
|
|
cmd := &cli.Command{
|
|
Flags: []cli.Flag{
|
|
// Config fetch tuning.
|
|
&cli.DurationFlag{Name: "forge-timeout", Value: defaultTimeout},
|
|
&cli.UintFlag{Name: "forge-retry", Value: defaultRetry},
|
|
&cli.StringSliceFlag{Name: "environment"},
|
|
// Forge flags — gitea=true satisfies setupForgeService's type switch.
|
|
&cli.BoolFlag{Name: string(TestForgeType), Value: true},
|
|
&cli.StringFlag{Name: "forge-url", Value: "https://forge.example.test"},
|
|
},
|
|
}
|
|
|
|
setupForge := services.SetupForge(func(*model.Forge) (forge.Forge, error) {
|
|
return mockForge, nil
|
|
})
|
|
|
|
return services.NewManager(cmd, s, setupForge)
|
|
}
|
|
|
|
// startGRPCServer binds to a random TCP port, registers Woodpecker's gRPC
|
|
// services, and starts serving. Shutdown happens via t.Cleanup.
|
|
func startGRPCServer(ctx context.Context, t *testing.T, s store.Store) string {
|
|
t.Helper()
|
|
|
|
lis, err := net.Listen("tcp", "127.0.0.1:0")
|
|
require.NoError(t, err, "listen on random port for gRPC")
|
|
addr := lis.Addr().String()
|
|
|
|
jwtManager := server_rpc.NewJWTManager(TestJWTSecret)
|
|
authorizer := server_rpc.NewAuthorizer(jwtManager)
|
|
|
|
grpcServer := grpc.NewServer(
|
|
grpc.StreamInterceptor(authorizer.StreamInterceptor),
|
|
grpc.UnaryInterceptor(authorizer.UnaryInterceptor),
|
|
grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
|
|
MinTime: shortTimeout,
|
|
}),
|
|
)
|
|
|
|
proto.RegisterWoodpeckerServer(grpcServer, server_rpc.NewTestWoodpeckerServer(
|
|
server.Config.Services.Scheduler,
|
|
server.Config.Services.Logs,
|
|
s,
|
|
prometheus.NewRegistry(),
|
|
))
|
|
proto.RegisterWoodpeckerAuthServer(grpcServer, server_rpc.NewWoodpeckerAuthServer(
|
|
jwtManager,
|
|
TestAgentToken,
|
|
s,
|
|
))
|
|
|
|
stopped := make(chan struct{})
|
|
grpcCtx, grpcCancel := context.WithCancelCause(ctx)
|
|
go func() {
|
|
<-grpcCtx.Done()
|
|
grpcServer.GracefulStop()
|
|
close(stopped)
|
|
}()
|
|
go func() {
|
|
if err := grpcServer.Serve(lis); err != nil {
|
|
grpcCancel(err)
|
|
}
|
|
}()
|
|
|
|
t.Cleanup(func() {
|
|
grpcCancel(nil)
|
|
<-stopped
|
|
})
|
|
return addr
|
|
}
|