rdev/internal/handlers/infrastructure_pipelines_test.go

327 lines
8.8 KiB
Go

package handlers
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/go-chi/chi/v5"
"github.com/orchard9/rdev/internal/domain"
"github.com/orchard9/rdev/internal/port"
)
// mockCIProvider implements port.CIProvider for testing.
type mockCIProvider struct {
pipelines map[string][]*domain.CIPipeline
err error
}
func newMockCIProvider() *mockCIProvider {
return &mockCIProvider{pipelines: make(map[string][]*domain.CIPipeline)}
}
func (m *mockCIProvider) ActivateRepo(context.Context, string, string, string) (*domain.CIRepo, error) {
return nil, m.err
}
func (m *mockCIProvider) DeactivateRepo(context.Context, string, string) error {
return m.err
}
func (m *mockCIProvider) GetRepo(context.Context, string, string) (*domain.CIRepo, error) {
return nil, m.err
}
func (m *mockCIProvider) ListRepos(context.Context) ([]*domain.CIRepo, error) {
return nil, m.err
}
func (m *mockCIProvider) AddSecret(context.Context, string, string, domain.CISecret) error {
return m.err
}
func (m *mockCIProvider) DeleteSecret(context.Context, string, string, string) error {
return m.err
}
func (m *mockCIProvider) ListPipelines(_ context.Context, owner, repo string) ([]*domain.CIPipeline, error) {
if m.err != nil {
return nil, m.err
}
key := owner + "/" + repo
p, ok := m.pipelines[key]
if !ok {
return nil, fmt.Errorf("repo not found: %s", key)
}
return p, nil
}
func (m *mockCIProvider) GetPipeline(_ context.Context, owner, repo string, number int64) (*domain.CIPipeline, error) {
if m.err != nil {
return nil, m.err
}
key := owner + "/" + repo
for _, p := range m.pipelines[key] {
if p.Number == number {
return p, nil
}
}
return nil, fmt.Errorf("pipeline %d not found", number)
}
func (m *mockCIProvider) TriggerBuild(_ context.Context, owner, repo, branch string) (int64, error) {
if m.err != nil {
return 0, m.err
}
return 1, nil
}
func (m *mockCIProvider) GetPipelineSteps(_ context.Context, owner, repo string, number int64) (*domain.CIPipelineSteps, error) {
if m.err != nil {
return nil, m.err
}
key := owner + "/" + repo
// Check if pipeline exists
for _, p := range m.pipelines[key] {
if p.Number == number {
return &domain.CIPipelineSteps{
PipelineNumber: number,
URL: fmt.Sprintf("https://ci.example.com/%s/%d", key, number),
Steps: []domain.CIPipelineStep{
{ID: 1, Name: "clone", Status: "success", Duration: 5},
{ID: 2, Name: "build", Status: "success", Duration: 30},
{ID: 3, Name: "test", Status: "success", Duration: 15},
},
}, nil
}
}
return nil, fmt.Errorf("pipeline %d not found", number)
}
func setupInfraHandlerWithCI(ci port.CIProvider) chi.Router {
h := NewInfrastructureHandler(nil, nil, nil, nil, ci, nil, InfrastructureConfig{
DefaultGitOwner: "threesix",
DefaultDomain: "threesix.ai",
})
r := chi.NewRouter()
h.Mount(r)
return r
}
func TestInfrastructureHandler_ListPipelines(t *testing.T) {
t.Run("success", func(t *testing.T) {
ci := newMockCIProvider()
ci.pipelines["threesix/myapp"] = []*domain.CIPipeline{
{
ID: 100,
Number: 1,
Status: "success",
Event: "push",
Branch: "main",
Commit: "abc123",
Message: "initial commit",
Author: "dev",
Started: time.Date(2025, 1, 15, 10, 0, 0, 0, time.UTC),
},
{
ID: 101,
Number: 2,
Status: "running",
Event: "push",
Branch: "feature",
Commit: "def456",
Author: "dev",
},
}
router := setupInfraHandlerWithCI(ci)
req := httptest.NewRequest("GET", "/projects/myapp/pipelines", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Errorf("status = %d, want %d", rec.Code, http.StatusOK)
}
})
t.Run("ci not configured", func(t *testing.T) {
router := setupInfraHandlerWithCI(nil)
req := httptest.NewRequest("GET", "/projects/myapp/pipelines", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusInternalServerError {
t.Errorf("status = %d, want %d", rec.Code, http.StatusInternalServerError)
}
})
t.Run("repo not found", func(t *testing.T) {
ci := newMockCIProvider()
router := setupInfraHandlerWithCI(ci)
req := httptest.NewRequest("GET", "/projects/missing/pipelines", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusNotFound {
t.Errorf("status = %d, want %d", rec.Code, http.StatusNotFound)
}
})
t.Run("invalid project id", func(t *testing.T) {
ci := newMockCIProvider()
router := setupInfraHandlerWithCI(ci)
req := httptest.NewRequest("GET", "/projects/INVALID!/pipelines", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusBadRequest {
t.Errorf("status = %d, want %d", rec.Code, http.StatusBadRequest)
}
})
}
func TestInfrastructureHandler_GetPipeline(t *testing.T) {
t.Run("success", func(t *testing.T) {
ci := newMockCIProvider()
ci.pipelines["threesix/myapp"] = []*domain.CIPipeline{
{
ID: 100,
Number: 5,
Status: "success",
Event: "push",
Branch: "main",
Commit: "abc123",
Message: "fix bug",
Author: "dev",
Started: time.Date(2025, 1, 15, 10, 0, 0, 0, time.UTC),
Finished: time.Date(2025, 1, 15, 10, 5, 0, 0, time.UTC),
},
}
router := setupInfraHandlerWithCI(ci)
req := httptest.NewRequest("GET", "/projects/myapp/pipelines/5", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Errorf("status = %d, want %d", rec.Code, http.StatusOK)
}
})
t.Run("ci not configured", func(t *testing.T) {
router := setupInfraHandlerWithCI(nil)
req := httptest.NewRequest("GET", "/projects/myapp/pipelines/1", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusInternalServerError {
t.Errorf("status = %d, want %d", rec.Code, http.StatusInternalServerError)
}
})
t.Run("pipeline not found", func(t *testing.T) {
ci := newMockCIProvider()
ci.pipelines["threesix/myapp"] = []*domain.CIPipeline{}
router := setupInfraHandlerWithCI(ci)
req := httptest.NewRequest("GET", "/projects/myapp/pipelines/999", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusNotFound {
t.Errorf("status = %d, want %d", rec.Code, http.StatusNotFound)
}
})
t.Run("invalid pipeline number", func(t *testing.T) {
ci := newMockCIProvider()
router := setupInfraHandlerWithCI(ci)
req := httptest.NewRequest("GET", "/projects/myapp/pipelines/notanumber", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusBadRequest {
t.Errorf("status = %d, want %d", rec.Code, http.StatusBadRequest)
}
})
t.Run("invalid project id", func(t *testing.T) {
ci := newMockCIProvider()
router := setupInfraHandlerWithCI(ci)
req := httptest.NewRequest("GET", "/projects/INVALID!/pipelines/1", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusBadRequest {
t.Errorf("status = %d, want %d", rec.Code, http.StatusBadRequest)
}
})
}
func TestInfrastructureHandler_GetPipelineSteps(t *testing.T) {
t.Run("success", func(t *testing.T) {
ci := newMockCIProvider()
ci.pipelines["threesix/myapp"] = []*domain.CIPipeline{
{
ID: 100,
Number: 5,
Status: "failure",
},
}
router := setupInfraHandlerWithCI(ci)
req := httptest.NewRequest("GET", "/projects/myapp/pipelines/5/steps", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Errorf("status = %d, want %d, body: %s", rec.Code, http.StatusOK, rec.Body.String())
}
})
t.Run("ci not configured", func(t *testing.T) {
router := setupInfraHandlerWithCI(nil)
req := httptest.NewRequest("GET", "/projects/myapp/pipelines/1/steps", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusInternalServerError {
t.Errorf("status = %d, want %d", rec.Code, http.StatusInternalServerError)
}
})
t.Run("pipeline not found", func(t *testing.T) {
ci := newMockCIProvider()
ci.pipelines["threesix/myapp"] = []*domain.CIPipeline{}
router := setupInfraHandlerWithCI(ci)
req := httptest.NewRequest("GET", "/projects/myapp/pipelines/999/steps", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusNotFound {
t.Errorf("status = %d, want %d", rec.Code, http.StatusNotFound)
}
})
}
func TestFormatTime(t *testing.T) {
t.Run("non-zero time", func(t *testing.T) {
ts := time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC)
got := formatTime(ts)
want := "2025-01-15T10:30:00Z"
if got != want {
t.Errorf("formatTime() = %q, want %q", got, want)
}
})
t.Run("zero time", func(t *testing.T) {
got := formatTime(time.Time{})
if got != "" {
t.Errorf("formatTime(zero) = %q, want empty string", got)
}
})
}