package buildkite
import (
"context"
"errors"
"net/http"
"testing"
"github.com/buildkite/go-buildkite/v4"
"github.com/stretchr/testify/require"
)
type MockPipelinesClient struct {
GetFunc func(ctx context.Context, org string, pipeline string) (buildkite.Pipeline, *buildkite.Response, error)
ListFunc func(ctx context.Context, org string, opt *buildkite.PipelineListOptions) ([]buildkite.Pipeline, *buildkite.Response, error)
CreateFunc func(ctx context.Context, org string, p buildkite.CreatePipeline) (buildkite.Pipeline, *buildkite.Response, error)
UpdateFunc func(ctx context.Context, org string, pipeline string, p buildkite.UpdatePipeline) (buildkite.Pipeline, *buildkite.Response, error)
AddWebhookFunc func(ctx context.Context, org string, slug string) (*buildkite.Response, error)
}
func (m *MockPipelinesClient) Get(ctx context.Context, org string, pipeline string) (buildkite.Pipeline, *buildkite.Response, error) {
if m.GetFunc != nil {
return m.GetFunc(ctx, org, pipeline)
}
return buildkite.Pipeline{}, nil, nil
}
func (m *MockPipelinesClient) List(ctx context.Context, org string, opt *buildkite.PipelineListOptions) ([]buildkite.Pipeline, *buildkite.Response, error) {
if m.ListFunc != nil {
return m.ListFunc(ctx, org, opt)
}
return nil, nil, nil
}
func (m *MockPipelinesClient) Create(ctx context.Context, org string, p buildkite.CreatePipeline) (buildkite.Pipeline, *buildkite.Response, error) {
if m.CreateFunc != nil {
return m.CreateFunc(ctx, org, p)
}
return buildkite.Pipeline{}, nil, nil
}
func (m *MockPipelinesClient) Update(ctx context.Context, org string, pipeline string, p buildkite.UpdatePipeline) (buildkite.Pipeline, *buildkite.Response, error) {
if m.UpdateFunc != nil {
return m.UpdateFunc(ctx, org, pipeline, p)
}
return buildkite.Pipeline{}, nil, nil
}
func (m *MockPipelinesClient) AddWebhook(ctx context.Context, org string, slug string) (*buildkite.Response, error) {
if m.AddWebhookFunc != nil {
return m.AddWebhookFunc(ctx, org, slug)
}
return &buildkite.Response{Response: &http.Response{StatusCode: 201}}, nil
}
var _ PipelinesClient = (*MockPipelinesClient)(nil)
func TestListPipelines(t *testing.T) {
assert := require.New(t)
ctx := context.Background()
client := &MockPipelinesClient{
ListFunc: func(ctx context.Context, org string, opt *buildkite.PipelineListOptions) ([]buildkite.Pipeline, *buildkite.Response, error) {
return []buildkite.Pipeline{
{
ID: "123",
Slug: "test-pipeline",
Name: "Test Pipeline",
CreatedAt: &buildkite.Timestamp{},
},
}, &buildkite.Response{
Response: &http.Response{
StatusCode: 200,
},
}, nil
},
}
tool, handler, _ := ListPipelines(client)
assert.NotNil(tool)
assert.NotNil(handler)
request := createMCPRequest(t, map[string]any{})
args := ListPipelinesArgs{
OrgSlug: "org",
}
result, err := handler(ctx, request, args)
assert.NoError(err)
textContent := getTextResult(t, result)
assert.JSONEq(`{"headers":{"Link":""},"items":[{"id":"123","name":"Test Pipeline","slug":"test-pipeline","repository":"","default_branch":"","web_url":"","visibility":"","created_at":"0001-01-01T00:00:00Z"}]}`, textContent.Text)
}
func TestGetPipeline(t *testing.T) {
assert := require.New(t)
ctx := context.Background()
client := &MockPipelinesClient{
GetFunc: func(ctx context.Context, org string, pipeline string) (buildkite.Pipeline, *buildkite.Response, error) {
return buildkite.Pipeline{
ID: "123",
Slug: "test-pipeline",
Name: "Test Pipeline",
CreatedAt: &buildkite.Timestamp{},
}, &buildkite.Response{
Response: &http.Response{
StatusCode: 200,
},
}, nil
},
}
tool, handler, _ := GetPipeline(client)
assert.NotNil(tool)
assert.NotNil(handler)
request := createMCPRequest(t, map[string]any{})
args := GetPipelineArgs{
OrgSlug: "org",
PipelineSlug: "pipeline",
}
result, err := handler(ctx, request, args)
assert.NoError(err)
textContent := getTextResult(t, result)
assert.JSONEq(`{"id":"123","name":"Test Pipeline","slug":"test-pipeline","created_at":"0001-01-01T00:00:00Z","skip_queued_branch_builds":false,"cancel_running_branch_builds":false,"provider":{"id":"","webhook_url":"","settings":null}}`, textContent.Text)
}
func TestCreatePipeline(t *testing.T) {
assert := require.New(t)
testPipelineDefinition := `
agents:
queue: "something"
env:
TEST_ENV_VAR: "value"
steps:
- command: "echo Hello World"
key: "hello_step"
label: "Hello Step"
`
ctx := context.Background()
webhookCalled := false
client := &MockPipelinesClient{
CreateFunc: func(ctx context.Context, org string, p buildkite.CreatePipeline) (buildkite.Pipeline, *buildkite.Response, error) {
// validate required fields
assert.Equal("org", org)
assert.Equal("cluster-123", p.ClusterID)
assert.Equal("Test Pipeline", p.Name)
assert.Equal("https://example.com/repo.git", p.Repository)
assert.Equal(testPipelineDefinition, p.Configuration)
return buildkite.Pipeline{
ID: "123",
Slug: "test-pipeline",
Name: "Test Pipeline",
ClusterID: "cluster-123",
CreatedAt: &buildkite.Timestamp{},
Tags: []string{"tag1", "tag2"},
}, &buildkite.Response{
Response: &http.Response{
StatusCode: 200,
},
}, nil
},
AddWebhookFunc: func(ctx context.Context, org string, slug string) (*buildkite.Response, error) {
assert.Equal("org", org)
assert.Equal("test-pipeline", slug)
webhookCalled = true
return &buildkite.Response{
Response: &http.Response{
StatusCode: 201,
},
}, nil
},
}
tool, handler, _ := CreatePipeline(client)
assert.NotNil(tool)
assert.NotNil(handler)
request := createMCPRequest(t, map[string]any{})
args := CreatePipelineArgs{
OrgSlug: "org",
Name: "Test Pipeline",
ClusterID: "cluster-123",
RepositoryURL: "https://example.com/repo.git",
Description: "A test pipeline",
Configuration: testPipelineDefinition,
Tags: []string{"tag1", "tag2"},
CreateWebhook: true, // should create webhook by default
}
result, err := handler(ctx, request, args)
assert.NoError(err)
assert.True(webhookCalled, "AddWebhook should have been called when CreateWebhook is true")
textContent := getTextResult(t, result)
assert.Contains(textContent.Text, `"webhook":{"created":true,"note":"Pipeline and webhook created successfully."}`)
assert.Contains(textContent.Text, `"pipeline":{"id":"123","name":"Test Pipeline","slug":"test-pipeline"`)
}
func TestCreatePipelineWithWebhook(t *testing.T) {
assert := require.New(t)
testPipelineDefinition := `
agents:
queue: "something"
env:
TEST_ENV_VAR: "value"
steps:
- command: "echo Hello World"
key: "hello_step"
label: "Hello Step"
`
ctx := context.Background()
webhookCalled := false
client := &MockPipelinesClient{
CreateFunc: func(ctx context.Context, org string, p buildkite.CreatePipeline) (buildkite.Pipeline, *buildkite.Response, error) {
// validate required fields
assert.Equal("org", org)
assert.Equal("Test Pipeline", p.Name)
assert.Equal("https://github.com/example/repo.git", p.Repository)
assert.Equal("cluster-123", p.ClusterID)
assert.Equal(testPipelineDefinition, p.Configuration)
return buildkite.Pipeline{
ID: "123",
Slug: "test-pipeline",
Name: "Test Pipeline",
ClusterID: "cluster-123",
CreatedAt: &buildkite.Timestamp{},
Tags: []string{"tag1", "tag2"},
}, &buildkite.Response{
Response: &http.Response{
StatusCode: 201,
},
}, nil
},
AddWebhookFunc: func(ctx context.Context, org string, slug string) (*buildkite.Response, error) {
// validate required fields
assert.Equal("org", org)
assert.Equal("test-pipeline", slug)
webhookCalled = true
return &buildkite.Response{
Response: &http.Response{
StatusCode: 201,
},
}, nil
},
}
tool, handler, _ := CreatePipeline(client)
assert.NotNil(tool)
assert.NotNil(handler)
request := createMCPRequest(t, map[string]any{})
args := CreatePipelineArgs{
OrgSlug: "org",
Name: "Test Pipeline",
ClusterID: "cluster-123",
RepositoryURL: "https://github.com/example/repo.git",
Description: "A test pipeline",
Configuration: testPipelineDefinition,
Tags: []string{"tag1", "tag2"},
CreateWebhook: true,
}
result, err := handler(ctx, request, args)
assert.NoError(err)
assert.True(webhookCalled, "AddWebhook should have been called")
textContent := getTextResult(t, result)
assert.Contains(textContent.Text, `"webhook":{"created":true,"note":"Pipeline and webhook created successfully."}`)
assert.Contains(textContent.Text, `"pipeline":{"id":"123","name":"Test Pipeline","slug":"test-pipeline"`)
}
func TestCreatePipelineWithWebhookError(t *testing.T) {
assert := require.New(t)
testPipelineDefinition := `
agents:
queue: "something"
env:
TEST_ENV_VAR: "value"
steps:
- command: "echo Hello World"
key: "hello_step"
label: "Hello Step"
`
ctx := context.Background()
webhookCalled := false
client := &MockPipelinesClient{
CreateFunc: func(ctx context.Context, org string, p buildkite.CreatePipeline) (buildkite.Pipeline, *buildkite.Response, error) {
// validate required fields
assert.Equal("org", org)
assert.Equal("Test Pipeline", p.Name)
assert.Equal("https://github.com/example/repo.git", p.Repository)
assert.Equal("cluster-123", p.ClusterID)
assert.Equal(testPipelineDefinition, p.Configuration)
return buildkite.Pipeline{
ID: "123",
Slug: "test-pipeline",
Name: "Test Pipeline",
ClusterID: "cluster-123",
CreatedAt: &buildkite.Timestamp{},
Tags: []string{"tag1", "tag2"},
}, &buildkite.Response{
Response: &http.Response{
StatusCode: 201,
},
}, nil
},
AddWebhookFunc: func(ctx context.Context, org string, slug string) (*buildkite.Response, error) {
webhookCalled = true
return nil, errors.New("Auto-creating webhooks is not supported for your repository.")
},
}
tool, handler, _ := CreatePipeline(client)
assert.NotNil(tool)
assert.NotNil(handler)
request := createMCPRequest(t, map[string]any{})
args := CreatePipelineArgs{
OrgSlug: "org",
Name: "Test Pipeline",
ClusterID: "cluster-123",
RepositoryURL: "https://github.com/example/repo.git",
Description: "A test pipeline",
Configuration: testPipelineDefinition,
Tags: []string{"tag1", "tag2"},
CreateWebhook: true,
}
result, err := handler(ctx, request, args)
assert.NoError(err)
assert.True(webhookCalled, "AddWebhook should have been called")
textContent := getTextResult(t, result)
assert.Contains(textContent.Text, `"webhook":{"created":false,`)
assert.Contains(textContent.Text, `"error":"Auto-creating webhooks is not supported for your repository."`)
assert.Contains(textContent.Text, `"note":"Pipeline created successfully, but webhook creation failed.`)
}
func TestUpdatePipeline(t *testing.T) {
assert := require.New(t)
testPipelineDefinition := `agents:
queue: "something"
env:
TEST_ENV_VAR: "value"
steps:
- command: "echo Hello World"
key: "hello_step"
label: "Hello Step"
`
ctx := context.Background()
client := &MockPipelinesClient{
UpdateFunc: func(ctx context.Context, org string, pipeline string, p buildkite.UpdatePipeline) (buildkite.Pipeline, *buildkite.Response, error) {
// validate required fields
assert.Equal("org", org)
assert.Equal("test-pipeline", pipeline)
assert.Equal(testPipelineDefinition, p.Configuration)
return buildkite.Pipeline{
ID: "123",
Slug: "test-pipeline",
Name: "Test Pipeline",
ClusterID: "abc-123",
CreatedAt: &buildkite.Timestamp{},
Tags: []string{"tag1", "tag2"},
}, &buildkite.Response{
Response: &http.Response{
StatusCode: 200,
},
}, nil
},
}
tool, handler, _ := UpdatePipeline(client)
assert.NotNil(tool)
assert.NotNil(handler)
request := createMCPRequest(t, map[string]any{})
args := UpdatePipelineArgs{
OrgSlug: "org",
PipelineSlug: "test-pipeline",
Name: "Test Pipeline",
ClusterID: "abc-123",
Description: "A test pipeline",
Configuration: testPipelineDefinition,
RepositoryURL: "https://example.com/repo.git",
Tags: []string{"tag1", "tag2"},
}
result, err := handler(ctx, request, args)
assert.NoError(err)
textContent := getTextResult(t, result)
assert.JSONEq(`{"id":"123","name":"Test Pipeline","slug":"test-pipeline","created_at":"0001-01-01T00:00:00Z","skip_queued_branch_builds":false,"cancel_running_branch_builds":false,"cluster_id":"abc-123","tags":["tag1","tag2"],"provider":{"id":"","webhook_url":"","settings":null}}`, textContent.Text)
}