package mcp import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestCreateSubtaskTool(t *testing.T) { deps := testDeps() // parent _, err := callTool(ctxWithAuth(Scope{}), deps, "create_issue", map[string]any{ "project_slug": "test-project", "title": "Parent", }) require.NoError(t, err) result, err := callTool(ctxWithAuth(Scope{}), deps, "create_subtask", map[string]any{ "project_slug": "test-project", "parent_number": float64(1), "title": "Child", "priority": float64(2), }) require.NoError(t, err) assert.False(t, result.IsError) var out issueCreateOut unmarshalStructured(t, result, &out) assert.True(t, out.OK) assert.Equal(t, "Child", out.Issue.Title) } func TestAddListRemoveDependencyTool(t *testing.T) { deps := testDeps() for _, title := range []string{"A", "B"} { _, err := callTool(ctxWithAuth(Scope{}), deps, "create_issue", map[string]any{ "project_slug": "test-project", "title": title, }) require.NoError(t, err) } // A(#1) blocked-by B(#2) result, err := callTool(ctxWithAuth(Scope{}), deps, "add_dependency", map[string]any{ "project_slug": "test-project", "blocked_number": float64(1), "blocker_number": float64(2), }) require.NoError(t, err) assert.False(t, result.IsError) var addOut dependencyOut unmarshalStructured(t, result, &addOut) assert.True(t, addOut.OK) // list_dependencies on A → blocked_by contains B result, err = callTool(ctxWithAuth(Scope{}), deps, "list_dependencies", map[string]any{ "project_slug": "test-project", "number": float64(1), }) require.NoError(t, err) var listOut listDependenciesOut unmarshalStructured(t, result, &listOut) require.Len(t, listOut.BlockedBy, 1) assert.Equal(t, 2, listOut.BlockedBy[0].Number) // remove result, err = callTool(ctxWithAuth(Scope{}), deps, "remove_dependency", map[string]any{ "project_slug": "test-project", "blocked_number": float64(1), "blocker_number": float64(2), }) require.NoError(t, err) assert.False(t, result.IsError) result, err = callTool(ctxWithAuth(Scope{}), deps, "list_dependencies", map[string]any{ "project_slug": "test-project", "number": float64(1), }) require.NoError(t, err) unmarshalStructured(t, result, &listOut) assert.Empty(t, listOut.BlockedBy) } func TestAddDependencyTool_RejectsSelf(t *testing.T) { deps := testDeps() _, err := callTool(ctxWithAuth(Scope{}), deps, "create_issue", map[string]any{ "project_slug": "test-project", "title": "A", }) require.NoError(t, err) result, err := callTool(ctxWithAuth(Scope{}), deps, "add_dependency", map[string]any{ "project_slug": "test-project", "blocked_number": float64(1), "blocker_number": float64(1), }) require.NoError(t, err) assert.True(t, result.IsError, "自依赖应被工具拒绝") } func TestSubmitDecompositionTool(t *testing.T) { deps := testDeps() // anchor parent _, err := callTool(ctxWithAuth(Scope{}), deps, "create_issue", map[string]any{ "project_slug": "test-project", "title": "Anchor", }) require.NoError(t, err) result, err := callTool(ctxWithAuth(Scope{}), deps, "submit_decomposition", map[string]any{ "project_slug": "test-project", "parent_number": float64(1), "subtasks": []any{ map[string]any{"ref": "a", "title": "Task A", "priority": float64(2)}, map[string]any{"ref": "b", "title": "Task B"}, }, "edges": []any{ map[string]any{"blocked_ref": "a", "blocker_ref": "b"}, }, }) require.NoError(t, err) assert.False(t, result.IsError) var out submitDecompositionOut unmarshalStructured(t, result, &out) assert.True(t, out.OK) require.Contains(t, out.RefToNumber, "a") require.Contains(t, out.RefToNumber, "b") } func TestSubmitDecompositionTool_RejectsCycle(t *testing.T) { deps := testDeps() result, err := callTool(ctxWithAuth(Scope{}), deps, "submit_decomposition", map[string]any{ "project_slug": "test-project", "subtasks": []any{ map[string]any{"ref": "a", "title": "A"}, map[string]any{"ref": "b", "title": "B"}, }, "edges": []any{ map[string]any{"blocked_ref": "a", "blocker_ref": "b"}, map[string]any{"blocked_ref": "b", "blocker_ref": "a"}, }, }) require.NoError(t, err) assert.True(t, result.IsError, "环应被拒绝") } func TestDecompositionToolsInSystemScope(t *testing.T) { // 新工具必须在 system token 默认白名单内,规划阶段 agent 才能调用。 set := map[string]bool{} for _, name := range defaultSystemTools { set[name] = true } for _, name := range []string{ "create_subtask", "add_dependency", "remove_dependency", "list_dependencies", "submit_decomposition", } { assert.True(t, set[name], "%s 应在 defaultSystemTools", name) } // create_acp_session 仍对 system token 禁用(不应在白名单)。 assert.False(t, set["create_acp_session"], "create_acp_session 不应在 system 白名单") }