Files
agentic-coding-workflow/internal/mcp/tools_run_test.go
T
2026-06-11 08:21:17 +08:00

199 lines
6.7 KiB
Go

package mcp
import (
"context"
"testing"
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/yan1h/agent-coding-workflow/internal/infra/errs"
"github.com/yan1h/agent-coding-workflow/internal/run"
"github.com/yan1h/agent-coding-workflow/internal/workspace"
)
// ===== fakes =====
// fakeRunSvc implements RunService.
type fakeRunSvc struct {
profiles []run.RunProfileResp
lastOp string
logs []run.LogLineResp
}
func (f *fakeRunSvc) List(_ context.Context, _ workspace.Caller, wsID uuid.UUID) ([]run.RunProfileResp, error) {
out := make([]run.RunProfileResp, 0, len(f.profiles))
for _, p := range f.profiles {
if p.WorkspaceID == wsID.String() {
out = append(out, p)
}
}
return out, nil
}
func (f *fakeRunSvc) Create(_ context.Context, _ workspace.Caller, wsID uuid.UUID, req run.CreateRunProfileReq) (run.RunProfileResp, error) {
p := run.RunProfileResp{
ID: uuid.NewString(), WorkspaceID: wsID.String(),
Slug: req.Slug, Name: req.Name, Command: req.Command,
Status: string(run.StatusStopped),
}
f.profiles = append(f.profiles, p)
return p, nil
}
func (f *fakeRunSvc) Update(_ context.Context, _ workspace.Caller, profileID uuid.UUID, req run.UpdateRunProfileReq) (run.RunProfileResp, error) {
for i := range f.profiles {
if f.profiles[i].ID == profileID.String() {
if req.Name != nil {
f.profiles[i].Name = *req.Name
}
return f.profiles[i], nil
}
}
return run.RunProfileResp{}, errs.New(errs.CodeNotFound, "profile")
}
func (f *fakeRunSvc) Delete(_ context.Context, _ workspace.Caller, profileID uuid.UUID) error {
kept := f.profiles[:0]
for _, p := range f.profiles {
if p.ID != profileID.String() {
kept = append(kept, p)
}
}
f.profiles = kept
return nil
}
func (f *fakeRunSvc) Start(_ context.Context, _ workspace.Caller, _ uuid.UUID) (run.RunStatusResp, error) {
f.lastOp = "start"
return run.RunStatusResp{Status: string(run.StatusRunning)}, nil
}
func (f *fakeRunSvc) Stop(_ context.Context, _ workspace.Caller, _ uuid.UUID) (run.RunStatusResp, error) {
f.lastOp = "stop"
return run.RunStatusResp{Status: string(run.StatusStopped)}, nil
}
func (f *fakeRunSvc) Restart(_ context.Context, _ workspace.Caller, _ uuid.UUID) (run.RunStatusResp, error) {
f.lastOp = "restart"
return run.RunStatusResp{Status: string(run.StatusRunning)}, nil
}
func (f *fakeRunSvc) Status(_ context.Context, _ workspace.Caller, _ uuid.UUID) (run.RunStatusResp, error) {
f.lastOp = "status"
return run.RunStatusResp{Status: string(run.StatusRunning)}, nil
}
func (f *fakeRunSvc) Logs(_ context.Context, _ workspace.Caller, _ uuid.UUID, _ int64, limit int) ([]run.LogLineResp, error) {
if len(f.logs) > limit {
return f.logs[:limit], nil
}
return f.logs, nil
}
func runTestDeps() (ServerDeps, *fakeRunSvc) {
deps := testDeps()
deps.Workspaces = &fakeWorkspaceSvc{items: []*workspace.Workspace{{
ID: testWSID, ProjectID: testPID, Slug: "ws-1", Name: "Workspace 1",
DefaultBranch: "main", SyncStatus: workspace.SyncStatusIdle, CreatedAt: time.Now(),
}}}
runSvc := &fakeRunSvc{}
deps.Runs = runSvc
return deps, runSvc
}
// ===== tests =====
func TestRunProfileCRUD(t *testing.T) {
deps, runSvc := runTestDeps()
result, err := callTool(ctxWithAuth(Scope{}), deps, "create_run_profile", map[string]any{
"workspace_id": testWSID.String(), "slug": "dev", "name": "Dev", "command": "pnpm dev",
})
require.NoError(t, err)
require.False(t, result.IsError)
var createOut runProfileOut
unmarshalStructured(t, result, &createOut)
assert.True(t, createOut.OK)
assert.Equal(t, "dev", createOut.Profile.Slug)
pid := createOut.Profile.ID
result, err = callTool(ctxWithAuth(Scope{}), deps, "list_run_profiles", map[string]any{
"workspace_id": testWSID.String(),
})
require.NoError(t, err)
var listOut listRunProfilesOut
unmarshalStructured(t, result, &listOut)
require.Len(t, listOut.Profiles, 1)
result, err = callTool(ctxWithAuth(Scope{}), deps, "update_run_profile", map[string]any{
"workspace_id": testWSID.String(), "profile_id": pid, "name": "Dev2",
})
require.NoError(t, err)
unmarshalStructured(t, result, &createOut)
assert.Equal(t, "Dev2", createOut.Profile.Name)
result, err = callTool(ctxWithAuth(Scope{}), deps, "delete_run_profile", map[string]any{
"workspace_id": testWSID.String(), "profile_id": pid,
})
require.NoError(t, err)
require.False(t, result.IsError)
assert.Empty(t, runSvc.profiles)
}
func TestRunProfileLifecycleOps(t *testing.T) {
deps, runSvc := runTestDeps()
p, err := runSvc.Create(context.Background(), workspace.Caller{}, testWSID, run.CreateRunProfileReq{
Slug: "dev", Name: "Dev", Command: "pnpm dev",
})
require.NoError(t, err)
for _, tc := range []struct{ tool, op, wantStatus string }{
{"start_run_profile", "start", string(run.StatusRunning)},
{"stop_run_profile", "stop", string(run.StatusStopped)},
{"restart_run_profile", "restart", string(run.StatusRunning)},
{"get_run_status", "status", string(run.StatusRunning)},
} {
result, err := callTool(ctxWithAuth(Scope{}), deps, tc.tool, map[string]any{
"workspace_id": testWSID.String(), "profile_id": p.ID,
})
require.NoError(t, err, tc.tool)
require.False(t, result.IsError, tc.tool)
var out runStatusOut
unmarshalStructured(t, result, &out)
assert.Equal(t, tc.op, runSvc.lastOp, tc.tool)
assert.Equal(t, tc.wantStatus, out.Status.Status, tc.tool)
}
}
func TestRunProfileMembershipEnforced(t *testing.T) {
deps, runSvc := runTestDeps()
// profile 属于其他 workspace → 不可经本 workspace 操作
runSvc.profiles = append(runSvc.profiles, run.RunProfileResp{
ID: uuid.NewString(), WorkspaceID: uuid.NewString(), Slug: "alien", Name: "Alien",
})
result, err := callTool(ctxWithAuth(Scope{}), deps, "start_run_profile", map[string]any{
"workspace_id": testWSID.String(), "profile_id": runSvc.profiles[0].ID,
})
require.NoError(t, err)
assert.True(t, result.IsError)
assert.Empty(t, runSvc.lastOp)
}
func TestGetRunLogs(t *testing.T) {
deps, runSvc := runTestDeps()
p, err := runSvc.Create(context.Background(), workspace.Caller{}, testWSID, run.CreateRunProfileReq{
Slug: "dev", Name: "Dev", Command: "pnpm dev",
})
require.NoError(t, err)
runSvc.logs = []run.LogLineResp{
{ID: 1, Level: "out", Text: "listening on :3000", TS: "2026-06-10T00:00:00Z"},
{ID: 2, Level: "err", Text: "warn: deprecated", TS: "2026-06-10T00:00:01Z"},
}
result, err := callTool(ctxWithAuth(Scope{}), deps, "get_run_logs", map[string]any{
"workspace_id": testWSID.String(), "profile_id": p.ID,
})
require.NoError(t, err)
require.False(t, result.IsError)
var out getRunLogsOut
unmarshalStructured(t, result, &out)
require.Len(t, out.Lines, 2)
assert.Equal(t, "listening on :3000", out.Lines[0].Text)
}