From 43e016e8fa40a7a1db96221c22b5274151ab271b Mon Sep 17 00:00:00 2001 From: Mathias Bergqvist Date: Mon, 4 May 2026 22:25:23 +0200 Subject: [PATCH] feat(tools): workflow_run_status --- internal/tools/workflow_run_status.go | 69 ++++++++++++++++++++++ internal/tools/workflow_run_status_test.go | 56 ++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 internal/tools/workflow_run_status.go create mode 100644 internal/tools/workflow_run_status_test.go diff --git a/internal/tools/workflow_run_status.go b/internal/tools/workflow_run_status.go new file mode 100644 index 0000000..45ce198 --- /dev/null +++ b/internal/tools/workflow_run_status.go @@ -0,0 +1,69 @@ +package tools + +import ( + "context" + "encoding/json" + "fmt" + + "gitea.d-ma.be/mathias/gitea-mcp/internal/allowlist" + "gitea.d-ma.be/mathias/gitea-mcp/internal/gitea" + "gitea.d-ma.be/mathias/gitea-mcp/internal/registry" +) + +// WorkflowRunStatus fetches the status of a Gitea Actions run. +type WorkflowRunStatus struct { + c *gitea.Client + a *allowlist.Allowlist +} + +func NewWorkflowRunStatus(c *gitea.Client, a *allowlist.Allowlist) *WorkflowRunStatus { + return &WorkflowRunStatus{c: c, a: a} +} + +func (t *WorkflowRunStatus) Descriptor() registry.ToolDescriptor { + return registry.ToolDescriptor{ + Name: "workflow_run_status", + Description: "Get the status of a Gitea Actions workflow run.", + InputSchema: json.RawMessage(`{ + "type":"object", + "properties":{ + "owner":{"type":"string"}, + "name":{"type":"string"}, + "run_id":{"type":"integer","minimum":1} + }, + "required":["owner","name","run_id"] + }`), + } +} + +type workflowRunStatusArgs struct { + Owner string `json:"owner"` + Name string `json:"name"` + RunID int64 `json:"run_id"` +} + +func (t *WorkflowRunStatus) Call(ctx context.Context, raw json.RawMessage) (json.RawMessage, error) { + var args workflowRunStatusArgs + if err := parseArgs(raw, &args); err != nil { + return nil, err + } + if err := t.a.Check(args.Owner); err != nil { + return nil, err + } + if args.RunID < 1 { + return nil, fmt.Errorf("run_id must be >= 1: %w", gitea.ErrValidation) + } + + run, err := t.c.GetWorkflowRun(ctx, args.Owner, args.Name, args.RunID) + if err != nil { + return nil, err + } + + return textOK(map[string]any{ + "run_id": run.ID, + "status": run.Status, + "conclusion": run.Conclusion, + "started_at": run.StartedAt, + "html_url": run.HTMLURL, + }) +} diff --git a/internal/tools/workflow_run_status_test.go b/internal/tools/workflow_run_status_test.go new file mode 100644 index 0000000..055d4ca --- /dev/null +++ b/internal/tools/workflow_run_status_test.go @@ -0,0 +1,56 @@ +package tools_test + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "gitea.d-ma.be/mathias/gitea-mcp/internal/allowlist" + "gitea.d-ma.be/mathias/gitea-mcp/internal/gitea" + "gitea.d-ma.be/mathias/gitea-mcp/internal/tools" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestWorkflowRunStatusTool(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/api/v1/repos/mathias/myrepo/actions/runs/789", r.URL.Path) + assert.Equal(t, http.MethodGet, r.Method) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{ + "id":789, + "status":"completed", + "conclusion":"success", + "started_at":"2026-05-04T10:00:00Z", + "html_url":"http://gitea.example/mathias/myrepo/actions/runs/789" + }`)) + })) + defer srv.Close() + + tool := tools.NewWorkflowRunStatus(gitea.NewClient(srv.URL, "tok"), allowlist.New([]string{"mathias"})) + out, err := tool.Call(context.Background(), json.RawMessage(`{"owner":"mathias","name":"myrepo","run_id":789}`)) + require.NoError(t, err) + + var result map[string]any + require.NoError(t, json.Unmarshal(out, &result)) + assert.Equal(t, float64(789), result["run_id"]) + assert.Equal(t, "completed", result["status"]) + assert.Equal(t, "success", result["conclusion"]) + assert.Equal(t, "2026-05-04T10:00:00Z", result["started_at"]) + assert.Equal(t, "http://gitea.example/mathias/myrepo/actions/runs/789", result["html_url"]) +} + +func TestWorkflowRunStatusAllowlistRejects(t *testing.T) { + tool := tools.NewWorkflowRunStatus(gitea.NewClient("http://unused", ""), allowlist.New([]string{"mathias"})) + _, err := tool.Call(context.Background(), json.RawMessage(`{"owner":"evil","name":"repo","run_id":1}`)) + require.Error(t, err) +} + +func TestWorkflowRunStatusRequiresValidRunID(t *testing.T) { + tool := tools.NewWorkflowRunStatus(gitea.NewClient("http://unused", ""), allowlist.New([]string{"mathias"})) + _, err := tool.Call(context.Background(), json.RawMessage(`{"owner":"mathias","name":"repo","run_id":0}`)) + require.Error(t, err) + assert.Contains(t, err.Error(), "run_id") +}