Mechanical formatting cleanup with no semantic changes. Required so the new gofmt-check step in .tekton/tasks/go-ci.yaml passes on first run.
316 lines
8.3 KiB
Go
316 lines
8.3 KiB
Go
package actions
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"codeberg.org/goern/forgejo-mcp/v2/pkg/forgejo"
|
|
forgejo_sdk "codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v3"
|
|
"github.com/mark3labs/mcp-go/mcp"
|
|
)
|
|
|
|
func newCallToolRequest(args map[string]interface{}) mcp.CallToolRequest {
|
|
return mcp.CallToolRequest{
|
|
Params: mcp.CallToolParams{
|
|
Arguments: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
// setupDispatchMockServer creates a mock server that expects a workflow dispatch POST
|
|
// and returns the captured request body and recorded path.
|
|
func setupDispatchMockServer(t *testing.T, statusCode int) (*httptest.Server, *string, *[]byte) {
|
|
t.Helper()
|
|
var capturedPath string
|
|
var capturedBody []byte
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
capturedPath = r.URL.Path
|
|
if r.Body != nil {
|
|
defer r.Body.Close()
|
|
buf := make([]byte, 1024)
|
|
n, _ := r.Body.Read(buf)
|
|
capturedBody = buf[:n]
|
|
}
|
|
w.WriteHeader(statusCode)
|
|
}))
|
|
|
|
client, err := forgejo_sdk.NewClient(srv.URL, forgejo_sdk.SetForgejoVersion("7.0.0"))
|
|
if err != nil {
|
|
t.Fatalf("creating test client: %v", err)
|
|
}
|
|
forgejo.SetClientForTesting(client)
|
|
|
|
return srv, &capturedPath, &capturedBody
|
|
}
|
|
|
|
func TestDispatchWorkflowFn_Success(t *testing.T) {
|
|
srv, capturedPath, _ := setupDispatchMockServer(t, http.StatusNoContent)
|
|
defer srv.Close()
|
|
|
|
req := newCallToolRequest(map[string]interface{}{
|
|
"owner": "testowner",
|
|
"repo": "testrepo",
|
|
"workflow": "ci.yml",
|
|
"ref": "main",
|
|
})
|
|
|
|
result, err := DispatchWorkflowFn(context.Background(), req)
|
|
if err != nil {
|
|
t.Fatalf("DispatchWorkflowFn returned error: %v", err)
|
|
}
|
|
if result.IsError {
|
|
t.Fatalf("DispatchWorkflowFn returned tool error")
|
|
}
|
|
|
|
expectedPath := "/api/v1/repos/testowner/testrepo/actions/workflows/ci.yml/dispatches"
|
|
if *capturedPath != expectedPath {
|
|
t.Errorf("wrong path: got %q, want %q", *capturedPath, expectedPath)
|
|
}
|
|
}
|
|
|
|
func TestDispatchWorkflowFn_WithInputs(t *testing.T) {
|
|
srv, _, capturedBody := setupDispatchMockServer(t, http.StatusNoContent)
|
|
defer srv.Close()
|
|
|
|
req := newCallToolRequest(map[string]interface{}{
|
|
"owner": "testowner",
|
|
"repo": "testrepo",
|
|
"workflow": "deploy.yml",
|
|
"ref": "v1.0.0",
|
|
"inputs": `{"environment": "production", "debug": "false"}`,
|
|
})
|
|
|
|
result, err := DispatchWorkflowFn(context.Background(), req)
|
|
if err != nil {
|
|
t.Fatalf("DispatchWorkflowFn returned error: %v", err)
|
|
}
|
|
if result.IsError {
|
|
t.Fatalf("DispatchWorkflowFn returned tool error")
|
|
}
|
|
|
|
var body map[string]interface{}
|
|
if err := json.Unmarshal(*capturedBody, &body); err != nil {
|
|
t.Fatalf("unmarshaling captured body: %v", err)
|
|
}
|
|
|
|
inputs, ok := body["inputs"].(map[string]interface{})
|
|
if !ok {
|
|
t.Fatalf("expected inputs in request body, got: %v", body)
|
|
}
|
|
if inputs["environment"] != "production" {
|
|
t.Errorf("expected environment=production, got %v", inputs["environment"])
|
|
}
|
|
}
|
|
|
|
func TestDispatchWorkflowFn_InvalidInputsJSON(t *testing.T) {
|
|
srv, _, _ := setupDispatchMockServer(t, http.StatusNoContent)
|
|
defer srv.Close()
|
|
|
|
req := newCallToolRequest(map[string]interface{}{
|
|
"owner": "testowner",
|
|
"repo": "testrepo",
|
|
"workflow": "ci.yml",
|
|
"ref": "main",
|
|
"inputs": `not-valid-json`,
|
|
})
|
|
|
|
_, err := DispatchWorkflowFn(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("expected error for invalid inputs JSON, got nil")
|
|
}
|
|
}
|
|
|
|
func TestDispatchWorkflowFn_MissingOwner(t *testing.T) {
|
|
req := newCallToolRequest(map[string]interface{}{
|
|
"repo": "testrepo",
|
|
"workflow": "ci.yml",
|
|
"ref": "main",
|
|
})
|
|
|
|
_, err := DispatchWorkflowFn(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("expected error for missing owner, got nil")
|
|
}
|
|
}
|
|
|
|
func TestDispatchWorkflowFn_MissingWorkflow(t *testing.T) {
|
|
req := newCallToolRequest(map[string]interface{}{
|
|
"owner": "testowner",
|
|
"repo": "testrepo",
|
|
"ref": "main",
|
|
})
|
|
|
|
_, err := DispatchWorkflowFn(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("expected error for missing workflow, got nil")
|
|
}
|
|
}
|
|
|
|
// setupListRunsMockServer creates a mock server that returns a list of workflow runs.
|
|
func setupListRunsMockServer(t *testing.T, response interface{}, statusCode int) (*httptest.Server, *string) {
|
|
t.Helper()
|
|
var capturedPath string
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
capturedPath = r.URL.Path
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(statusCode)
|
|
if response != nil {
|
|
json.NewEncoder(w).Encode(response)
|
|
}
|
|
}))
|
|
|
|
client, err := forgejo_sdk.NewClient(srv.URL, forgejo_sdk.SetForgejoVersion("7.0.0"))
|
|
if err != nil {
|
|
t.Fatalf("creating test client: %v", err)
|
|
}
|
|
forgejo.SetClientForTesting(client)
|
|
|
|
return srv, &capturedPath
|
|
}
|
|
|
|
func TestListWorkflowRunsFn_ReturnsRuns(t *testing.T) {
|
|
mockResponse := map[string]interface{}{
|
|
"total_count": 2,
|
|
"workflow_runs": []map[string]interface{}{
|
|
{
|
|
"id": 1,
|
|
"title": "Run CI",
|
|
"status": "success",
|
|
"event": "push",
|
|
"commit_sha": "abc1234567890",
|
|
"html_url": "https://example.com/runs/1",
|
|
},
|
|
{
|
|
"id": 2,
|
|
"title": "Deploy",
|
|
"status": "running",
|
|
"event": "workflow_dispatch",
|
|
"commit_sha": "def9876543210",
|
|
"html_url": "https://example.com/runs/2",
|
|
},
|
|
},
|
|
}
|
|
|
|
srv, capturedPath := setupListRunsMockServer(t, mockResponse, http.StatusOK)
|
|
defer srv.Close()
|
|
|
|
req := newCallToolRequest(map[string]interface{}{
|
|
"owner": "testowner",
|
|
"repo": "testrepo",
|
|
})
|
|
|
|
result, err := ListWorkflowRunsFn(context.Background(), req)
|
|
if err != nil {
|
|
t.Fatalf("ListWorkflowRunsFn returned error: %v", err)
|
|
}
|
|
if result.IsError {
|
|
t.Fatalf("ListWorkflowRunsFn returned tool error")
|
|
}
|
|
|
|
expectedPath := "/api/v1/repos/testowner/testrepo/actions/runs"
|
|
if *capturedPath != expectedPath {
|
|
t.Errorf("wrong path: got %q, want %q", *capturedPath, expectedPath)
|
|
}
|
|
}
|
|
|
|
func TestListWorkflowRunsFn_EmptyResult(t *testing.T) {
|
|
mockResponse := map[string]interface{}{
|
|
"total_count": 0,
|
|
"workflow_runs": []interface{}{},
|
|
}
|
|
|
|
srv, _ := setupListRunsMockServer(t, mockResponse, http.StatusOK)
|
|
defer srv.Close()
|
|
|
|
req := newCallToolRequest(map[string]interface{}{
|
|
"owner": "testowner",
|
|
"repo": "testrepo",
|
|
})
|
|
|
|
result, err := ListWorkflowRunsFn(context.Background(), req)
|
|
if err != nil {
|
|
t.Fatalf("ListWorkflowRunsFn returned error: %v", err)
|
|
}
|
|
if result.IsError {
|
|
t.Fatalf("ListWorkflowRunsFn returned tool error")
|
|
}
|
|
}
|
|
|
|
func TestListWorkflowRunsFn_MissingRepo(t *testing.T) {
|
|
req := newCallToolRequest(map[string]interface{}{
|
|
"owner": "testowner",
|
|
})
|
|
|
|
_, err := ListWorkflowRunsFn(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("expected error for missing repo, got nil")
|
|
}
|
|
}
|
|
|
|
func TestGetWorkflowRunFn_ReturnsRun(t *testing.T) {
|
|
mockResponse := map[string]interface{}{
|
|
"id": 42,
|
|
"title": "CI Pipeline",
|
|
"status": "success",
|
|
"event": "push",
|
|
"commit_sha": "deadbeef",
|
|
"prettyref": "refs/heads/main",
|
|
"html_url": "https://example.com/runs/42",
|
|
"trigger_user": map[string]interface{}{
|
|
"login": "testuser",
|
|
"full_name": "Test User",
|
|
},
|
|
}
|
|
|
|
srv, capturedPath := setupListRunsMockServer(t, mockResponse, http.StatusOK)
|
|
defer srv.Close()
|
|
|
|
req := newCallToolRequest(map[string]interface{}{
|
|
"owner": "testowner",
|
|
"repo": "testrepo",
|
|
"run_id": float64(42),
|
|
})
|
|
|
|
result, err := GetWorkflowRunFn(context.Background(), req)
|
|
if err != nil {
|
|
t.Fatalf("GetWorkflowRunFn returned error: %v", err)
|
|
}
|
|
if result.IsError {
|
|
t.Fatalf("GetWorkflowRunFn returned tool error")
|
|
}
|
|
|
|
expectedPath := "/api/v1/repos/testowner/testrepo/actions/runs/42"
|
|
if *capturedPath != expectedPath {
|
|
t.Errorf("wrong path: got %q, want %q", *capturedPath, expectedPath)
|
|
}
|
|
}
|
|
|
|
func TestGetWorkflowRunFn_InvalidRunID(t *testing.T) {
|
|
req := newCallToolRequest(map[string]interface{}{
|
|
"owner": "testowner",
|
|
"repo": "testrepo",
|
|
"run_id": float64(-1),
|
|
})
|
|
|
|
_, err := GetWorkflowRunFn(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("expected error for invalid run_id, got nil")
|
|
}
|
|
}
|
|
|
|
func TestGetWorkflowRunFn_MissingRunID(t *testing.T) {
|
|
req := newCallToolRequest(map[string]interface{}{
|
|
"owner": "testowner",
|
|
"repo": "testrepo",
|
|
})
|
|
|
|
_, err := GetWorkflowRunFn(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("expected error for missing run_id, got nil")
|
|
}
|
|
}
|