simplecov-mcp 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +98 -50
- data/docs/{ARCHITECTURE.md → dev/ARCHITECTURE.md} +11 -10
- data/docs/dev/BRANCH_ONLY_COVERAGE.md +158 -0
- data/docs/{DEVELOPMENT.md → dev/DEVELOPMENT.md} +2 -1
- data/docs/dev/README.md +10 -0
- data/docs/dev/RELEASING.md +146 -0
- data/docs/{arch-decisions → dev/arch-decisions}/001-x-arch-decision.md +3 -1
- data/docs/{arch-decisions → dev/arch-decisions}/002-x-arch-decision.md +7 -5
- data/docs/{arch-decisions → dev/arch-decisions}/003-x-arch-decision.md +2 -0
- data/docs/{arch-decisions → dev/arch-decisions}/004-x-arch-decision.md +6 -2
- data/docs/{arch-decisions → dev/arch-decisions}/005-x-arch-decision.md +4 -2
- data/docs/{arch-decisions → dev/arch-decisions}/README.md +3 -3
- data/docs/{presentations → dev/presentations}/simplecov-mcp-presentation.md +28 -22
- data/docs/fixtures/demo_project/README.md +9 -0
- data/docs/{ADVANCED_USAGE.md → user/ADVANCED_USAGE.md} +129 -319
- data/docs/user/CLI_FALLBACK_FOR_LLMS.md +34 -0
- data/docs/user/CLI_USAGE.md +750 -0
- data/docs/{ERROR_HANDLING.md → user/ERROR_HANDLING.md} +12 -12
- data/docs/user/EXAMPLES.md +588 -0
- data/docs/user/INSTALLATION.md +130 -0
- data/docs/{LIBRARY_API.md → user/LIBRARY_API.md} +90 -32
- data/docs/{MCP_INTEGRATION.md → user/MCP_INTEGRATION.md} +36 -34
- data/docs/user/README.md +14 -0
- data/docs/{TROUBLESHOOTING.md → user/TROUBLESHOOTING.md} +21 -100
- data/docs/user/V2-BREAKING-CHANGES.md +472 -0
- data/exe/simplecov-mcp +1 -1
- data/lib/simplecov_mcp/{cli_config.rb → app_config.rb} +12 -12
- data/lib/simplecov_mcp/app_context.rb +1 -1
- data/lib/simplecov_mcp/base_tool.rb +66 -38
- data/lib/simplecov_mcp/cli.rb +67 -123
- data/lib/simplecov_mcp/commands/base_command.rb +16 -27
- data/lib/simplecov_mcp/commands/command_factory.rb +8 -2
- data/lib/simplecov_mcp/commands/detailed_command.rb +16 -2
- data/lib/simplecov_mcp/commands/list_command.rb +1 -1
- data/lib/simplecov_mcp/commands/raw_command.rb +18 -2
- data/lib/simplecov_mcp/commands/summary_command.rb +20 -3
- data/lib/simplecov_mcp/commands/totals_command.rb +53 -0
- data/lib/simplecov_mcp/commands/uncovered_command.rb +24 -5
- data/lib/simplecov_mcp/commands/validate_command.rb +60 -0
- data/lib/simplecov_mcp/commands/version_command.rb +19 -4
- data/lib/simplecov_mcp/config_parser.rb +32 -0
- data/lib/simplecov_mcp/constants.rb +3 -3
- data/lib/simplecov_mcp/coverage_reporter.rb +31 -0
- data/lib/simplecov_mcp/error_handler.rb +81 -40
- data/lib/simplecov_mcp/error_handler_factory.rb +2 -2
- data/lib/simplecov_mcp/errors.rb +32 -20
- data/lib/simplecov_mcp/formatters/source_formatter.rb +23 -19
- data/lib/simplecov_mcp/formatters.rb +51 -0
- data/lib/simplecov_mcp/mcp_server.rb +9 -7
- data/lib/simplecov_mcp/mode_detector.rb +6 -5
- data/lib/simplecov_mcp/model.rb +122 -83
- data/lib/simplecov_mcp/option_normalizers.rb +39 -18
- data/lib/simplecov_mcp/option_parser_builder.rb +82 -65
- data/lib/simplecov_mcp/option_parsers/env_options_parser.rb +3 -5
- data/lib/simplecov_mcp/option_parsers/error_helper.rb +18 -17
- data/lib/simplecov_mcp/path_relativizer.rb +17 -14
- data/lib/simplecov_mcp/predicate_evaluator.rb +72 -0
- data/lib/simplecov_mcp/presenters/base_coverage_presenter.rb +1 -3
- data/lib/simplecov_mcp/presenters/coverage_detailed_presenter.rb +1 -3
- data/lib/simplecov_mcp/presenters/coverage_raw_presenter.rb +1 -3
- data/lib/simplecov_mcp/presenters/coverage_summary_presenter.rb +1 -3
- data/lib/simplecov_mcp/presenters/coverage_uncovered_presenter.rb +1 -3
- data/lib/simplecov_mcp/presenters/project_coverage_presenter.rb +1 -3
- data/lib/simplecov_mcp/presenters/project_totals_presenter.rb +27 -0
- data/lib/simplecov_mcp/resolvers/coverage_line_resolver.rb +14 -18
- data/lib/simplecov_mcp/resolvers/resultset_path_resolver.rb +7 -9
- data/lib/simplecov_mcp/resultset_loader.rb +20 -25
- data/lib/simplecov_mcp/staleness_checker.rb +50 -46
- data/lib/simplecov_mcp/table_formatter.rb +64 -0
- data/lib/simplecov_mcp/tools/all_files_coverage_tool.rb +20 -50
- data/lib/simplecov_mcp/tools/coverage_detailed_tool.rb +13 -7
- data/lib/simplecov_mcp/tools/coverage_raw_tool.rb +12 -7
- data/lib/simplecov_mcp/tools/coverage_summary_tool.rb +13 -8
- data/lib/simplecov_mcp/tools/coverage_table_tool.rb +20 -60
- data/lib/simplecov_mcp/tools/coverage_totals_tool.rb +44 -0
- data/lib/simplecov_mcp/tools/help_tool.rb +38 -66
- data/lib/simplecov_mcp/tools/uncovered_lines_tool.rb +13 -8
- data/lib/simplecov_mcp/tools/validate_tool.rb +72 -0
- data/lib/simplecov_mcp/tools/version_tool.rb +7 -14
- data/lib/simplecov_mcp/util.rb +18 -12
- data/lib/simplecov_mcp/version.rb +1 -1
- data/lib/simplecov_mcp.rb +23 -29
- data/spec/all_files_coverage_tool_spec.rb +4 -3
- data/spec/{cli_config_spec.rb → app_config_spec.rb} +31 -26
- data/spec/base_tool_spec.rb +17 -14
- data/spec/cli/show_default_report_spec.rb +2 -2
- data/spec/cli_enumerated_options_spec.rb +31 -9
- data/spec/cli_error_spec.rb +46 -23
- data/spec/cli_format_spec.rb +123 -0
- data/spec/cli_json_options_spec.rb +50 -0
- data/spec/cli_source_spec.rb +11 -63
- data/spec/cli_spec.rb +82 -97
- data/spec/cli_usage_spec.rb +15 -15
- data/spec/commands/base_command_spec.rb +12 -92
- data/spec/commands/command_factory_spec.rb +7 -3
- data/spec/commands/detailed_command_spec.rb +10 -24
- data/spec/commands/list_command_spec.rb +28 -0
- data/spec/commands/raw_command_spec.rb +43 -20
- data/spec/commands/summary_command_spec.rb +10 -23
- data/spec/commands/totals_command_spec.rb +34 -0
- data/spec/commands/uncovered_command_spec.rb +29 -23
- data/spec/commands/validate_command_spec.rb +213 -0
- data/spec/commands/version_command_spec.rb +38 -0
- data/spec/constants_spec.rb +3 -3
- data/spec/coverage_reporter_spec.rb +102 -0
- data/spec/coverage_table_tool_spec.rb +21 -10
- data/spec/coverage_totals_tool_spec.rb +37 -0
- data/spec/error_handler_spec.rb +120 -4
- data/spec/error_mode_spec.rb +18 -22
- data/spec/errors_edge_cases_spec.rb +101 -28
- data/spec/errors_stale_spec.rb +34 -0
- data/spec/file_based_mcp_tools_spec.rb +6 -6
- data/spec/fixtures/project1/lib/bar.rb +2 -0
- data/spec/fixtures/project1/lib/foo.rb +2 -0
- data/spec/help_tool_spec.rb +2 -18
- data/spec/integration_spec.rb +114 -170
- data/spec/logging_fallback_spec.rb +3 -3
- data/spec/mcp_server_integration_spec.rb +1 -1
- data/spec/mcp_server_spec.rb +70 -53
- data/spec/mode_detector_spec.rb +46 -41
- data/spec/model_error_handling_spec.rb +141 -82
- data/spec/model_staleness_spec.rb +13 -13
- data/spec/option_normalizers_spec.rb +111 -112
- data/spec/option_parsers/env_options_parser_spec.rb +25 -37
- data/spec/option_parsers/error_helper_spec.rb +56 -56
- data/spec/path_relativizer_spec.rb +15 -0
- data/spec/presenters/coverage_detailed_presenter_spec.rb +1 -1
- data/spec/presenters/coverage_summary_presenter_spec.rb +1 -1
- data/spec/presenters/coverage_uncovered_presenter_spec.rb +1 -1
- data/spec/presenters/project_coverage_presenter_spec.rb +9 -8
- data/spec/presenters/project_totals_presenter_spec.rb +144 -0
- data/spec/resolvers/coverage_line_resolver_spec.rb +261 -36
- data/spec/resolvers/resultset_path_resolver_spec.rb +13 -8
- data/spec/shared_examples/file_based_mcp_tools.rb +23 -18
- data/spec/shared_examples/formatted_command_examples.rb +64 -0
- data/spec/simple_cov_mcp_module_spec.rb +24 -3
- data/spec/simplecov_mcp/formatters/source_formatter_spec.rb +267 -0
- data/spec/simplecov_mcp/formatters_spec.rb +76 -0
- data/spec/simplecov_mcp/presenters/base_coverage_presenter_spec.rb +79 -0
- data/spec/simplecov_mcp_model_spec.rb +99 -49
- data/spec/simplecov_mcp_opts_spec.rb +42 -39
- data/spec/spec_helper.rb +27 -92
- data/spec/staleness_checker_spec.rb +10 -9
- data/spec/staleness_more_spec.rb +4 -4
- data/spec/support/cli_helpers.rb +22 -0
- data/spec/support/control_flow_helpers.rb +20 -0
- data/spec/support/fake_mcp.rb +40 -0
- data/spec/support/io_helpers.rb +29 -0
- data/spec/support/mcp_helpers.rb +35 -0
- data/spec/support/mcp_runner.rb +10 -8
- data/spec/support/mocking_helpers.rb +30 -0
- data/spec/table_format_spec.rb +70 -0
- data/spec/tools/validate_tool_spec.rb +132 -0
- data/spec/tools_error_handling_spec.rb +34 -48
- data/spec/util_spec.rb +5 -4
- data/spec/version_spec.rb +7 -7
- data/spec/version_tool_spec.rb +20 -22
- metadata +90 -23
- data/docs/BRANCH_ONLY_COVERAGE.md +0 -81
- data/docs/CLI_USAGE.md +0 -637
- data/docs/EXAMPLES.md +0 -430
- data/docs/INSTALLATION.md +0 -352
- data/spec/cli_success_predicate_spec.rb +0 -141
data/spec/integration_spec.rb
CHANGED
|
@@ -2,148 +2,74 @@
|
|
|
2
2
|
|
|
3
3
|
require 'spec_helper'
|
|
4
4
|
|
|
5
|
+
# Timeout for MCP server operations (increased for JRuby compatibility)
|
|
6
|
+
MCP_TIMEOUT = 5
|
|
7
|
+
|
|
5
8
|
RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
6
9
|
let(:project_root) { (FIXTURES_DIR / 'project1').to_s }
|
|
7
10
|
let(:coverage_dir) { File.join(project_root, 'coverage') }
|
|
8
11
|
let(:resultset_path) { File.join(coverage_dir, '.resultset.json') }
|
|
9
12
|
|
|
10
13
|
describe 'End-to-End Coverage Model Functionality' do
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# Initialize model with real fixture data
|
|
14
|
-
model = SimpleCovMcp::CoverageModel.new(root: project_root, resultset: coverage_dir)
|
|
15
|
-
|
|
16
|
-
# Test all_files returns real coverage data
|
|
17
|
-
all_files = model.all_files
|
|
18
|
-
expect(all_files).to be_an(Array)
|
|
19
|
-
expect(all_files.length).to eq(2)
|
|
20
|
-
|
|
21
|
-
# Verify file paths and coverage data structure
|
|
22
|
-
foo_file = all_files.find { |f| f['file'].include?('foo.rb') }
|
|
23
|
-
bar_file = all_files.find { |f| f['file'].include?('bar.rb') }
|
|
24
|
-
|
|
25
|
-
expect(foo_file).to include('covered', 'total', 'percentage', 'stale')
|
|
26
|
-
expect(bar_file).to include('covered', 'total', 'percentage', 'stale')
|
|
27
|
-
|
|
28
|
-
# Verify actual coverage calculations match fixture data
|
|
29
|
-
# foo.rb has coverage: [1, 0, nil, 2] -> 2 covered out of 3 executable = 66.67%
|
|
30
|
-
expect(foo_file['total']).to eq(3)
|
|
31
|
-
expect(foo_file['covered']).to eq(2)
|
|
32
|
-
expect(foo_file['percentage']).to be_within(0.01).of(66.67)
|
|
33
|
-
|
|
34
|
-
# bar.rb has coverage: [0, 0, 1] -> 1 covered out of 3 executable = 33.33%
|
|
35
|
-
expect(bar_file['total']).to eq(3)
|
|
36
|
-
expect(bar_file['covered']).to eq(1)
|
|
37
|
-
expect(bar_file['percentage']).to be_within(0.01).of(33.33)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
it 'provides detailed per-file analysis' do
|
|
41
|
-
model = SimpleCovMcp::CoverageModel.new(root: project_root, resultset: coverage_dir)
|
|
42
|
-
|
|
43
|
-
# Test raw coverage data
|
|
44
|
-
raw = model.raw_for('lib/foo.rb')
|
|
45
|
-
expect(raw['file']).to end_with('lib/foo.rb')
|
|
46
|
-
expect(raw['lines']).to eq([1, 0, nil, 2])
|
|
47
|
-
|
|
48
|
-
# Test summary calculation
|
|
49
|
-
summary = model.summary_for('lib/foo.rb')
|
|
50
|
-
expect(summary['file']).to end_with('lib/foo.rb')
|
|
51
|
-
expect(summary['summary']).to include('covered' => 2, 'total' => 3)
|
|
52
|
-
expect(summary['summary']['pct']).to be_within(0.01).of(66.67)
|
|
53
|
-
|
|
54
|
-
# Test uncovered lines detection
|
|
55
|
-
uncovered = model.uncovered_for('lib/foo.rb')
|
|
56
|
-
expect(uncovered['file']).to end_with('lib/foo.rb')
|
|
57
|
-
expect(uncovered['uncovered']).to eq([2]) # Line 2 has 0 hits
|
|
58
|
-
expect(uncovered['summary']).to include('covered' => 2, 'total' => 3)
|
|
59
|
-
|
|
60
|
-
# Test detailed line-by-line analysis
|
|
61
|
-
detailed = model.detailed_for('lib/foo.rb')
|
|
62
|
-
expect(detailed['file']).to end_with('lib/foo.rb')
|
|
63
|
-
expect(detailed['lines']).to eq([
|
|
64
|
-
{ 'line' => 1, 'hits' => 1, 'covered' => true },
|
|
65
|
-
{ 'line' => 2, 'hits' => 0, 'covered' => false },
|
|
66
|
-
{ 'line' => 4, 'hits' => 2, 'covered' => true }
|
|
67
|
-
])
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
it 'generates properly formatted coverage tables' do
|
|
71
|
-
model = SimpleCovMcp::CoverageModel.new(root: project_root, resultset: coverage_dir)
|
|
72
|
-
|
|
73
|
-
# Test default table generation
|
|
74
|
-
table = model.format_table
|
|
75
|
-
|
|
76
|
-
# Verify table structure (Unicode box drawing)
|
|
77
|
-
expect(table).to include('┌', '┬', '┐', '│', '├', '┼', '┤', '└', '┴', '┘')
|
|
78
|
-
|
|
79
|
-
# Verify headers
|
|
80
|
-
expect(table).to include('File', '%', 'Covered', 'Total', 'Stale')
|
|
14
|
+
it 'loads fixture coverage and surfaces core stats across APIs' do
|
|
15
|
+
model = SimpleCovMcp::CoverageModel.new(root: project_root, resultset: coverage_dir)
|
|
81
16
|
|
|
82
|
-
|
|
83
|
-
|
|
17
|
+
files = model.all_files
|
|
18
|
+
expect(files.length).to eq(2)
|
|
19
|
+
files_by_name = files.to_h { |f| [File.basename(f['file']), f] }
|
|
84
20
|
|
|
85
|
-
|
|
86
|
-
|
|
21
|
+
foo = files_by_name.fetch('foo.rb')
|
|
22
|
+
bar = files_by_name.fetch('bar.rb')
|
|
23
|
+
expect(foo['percentage']).to be_within(0.01).of(66.67)
|
|
24
|
+
expect(bar['percentage']).to be_within(0.01).of(33.33)
|
|
87
25
|
|
|
88
|
-
|
|
89
|
-
|
|
26
|
+
raw = model.raw_for('lib/foo.rb')
|
|
27
|
+
expect(raw['lines']).to eq([1, 0, nil, 2])
|
|
90
28
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
data_lines = lines.select { |line| line.include?('lib/') }
|
|
94
|
-
expect(data_lines.first).to include('lib/bar.rb') # Lower coverage first
|
|
95
|
-
expect(data_lines.last).to include('lib/foo.rb') # Higher coverage last
|
|
96
|
-
end
|
|
29
|
+
summary = model.summary_for('lib/foo.rb')
|
|
30
|
+
expect(summary['summary']).to include('covered' => 2, 'total' => 3)
|
|
97
31
|
|
|
98
|
-
|
|
99
|
-
|
|
32
|
+
uncovered = model.uncovered_for('lib/foo.rb')
|
|
33
|
+
expect(uncovered['uncovered']).to eq([2])
|
|
100
34
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
expect(asc_files.first['file']).to end_with('lib/bar.rb') # Lower coverage first
|
|
104
|
-
expect(asc_files.last['file']).to end_with('lib/foo.rb') # Higher coverage last
|
|
35
|
+
detailed = model.detailed_for('lib/foo.rb')
|
|
36
|
+
expect(detailed['lines']).to include({ 'line' => 2, 'hits' => 0, 'covered' => false })
|
|
105
37
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
38
|
+
table = model.format_table
|
|
39
|
+
expect(table).to include('lib/foo.rb', 'lib/bar.rb', '66.67', '33.33')
|
|
40
|
+
data_lines = table.split("\n").select { |line| line.include?('lib/') }
|
|
41
|
+
expect(data_lines.first).to include('lib/foo.rb') # Highest coverage first (descending default)
|
|
42
|
+
expect(data_lines.last).to include('lib/bar.rb')
|
|
111
43
|
end
|
|
112
44
|
end
|
|
113
45
|
|
|
114
46
|
describe 'CLI Integration with Real Coverage Data' do
|
|
115
47
|
it 'executes all major CLI commands without errors' do
|
|
116
48
|
# Test list command
|
|
117
|
-
list_output =
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
cli.run(['list', '--root', project_root, '--resultset', coverage_dir])
|
|
121
|
-
list_output = out.string
|
|
122
|
-
end
|
|
123
|
-
|
|
49
|
+
list_output, _err, status = run_cli_with_status('--root', project_root, '--resultset',
|
|
50
|
+
coverage_dir, 'list')
|
|
51
|
+
expect(status).to eq(0)
|
|
124
52
|
expect(list_output).to include('lib/foo.rb', 'lib/bar.rb')
|
|
125
53
|
expect(list_output).to include('66.67', '33.33')
|
|
54
|
+
data_lines = list_output.lines.select { |line| line.include?('lib/') }
|
|
55
|
+
expect(data_lines.first).to include('lib/foo.rb') # Highest coverage first (descending default)
|
|
56
|
+
expect(data_lines.last).to include('lib/bar.rb')
|
|
126
57
|
|
|
127
58
|
# Test summary command
|
|
128
|
-
summary_output =
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
expect(summary_output).to include('66.67%', '2/3')
|
|
59
|
+
summary_output, _err, status = run_cli_with_status('--root', project_root, '--resultset',
|
|
60
|
+
coverage_dir, 'summary', 'lib/foo.rb')
|
|
61
|
+
expect(status).to eq(0)
|
|
62
|
+
expect(summary_output).to include('│') # Table format
|
|
63
|
+
expect(summary_output).to include('66.67%')
|
|
64
|
+
expect(summary_output).to include('2')
|
|
65
|
+
expect(summary_output).to include('3')
|
|
136
66
|
|
|
137
67
|
# Test JSON output
|
|
138
|
-
json_output =
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
])
|
|
144
|
-
json_output = out.string
|
|
145
|
-
end
|
|
146
|
-
|
|
68
|
+
json_output, _err, status = run_cli_with_status(
|
|
69
|
+
'--format', 'json', '--root', project_root, '--resultset', coverage_dir,
|
|
70
|
+
'summary', 'lib/foo.rb'
|
|
71
|
+
)
|
|
72
|
+
expect(status).to eq(0)
|
|
147
73
|
json_data = JSON.parse(json_output)
|
|
148
74
|
expect(json_data).to include('file', 'summary')
|
|
149
75
|
expect(json_data['summary']).to include('covered' => 2, 'total' => 3)
|
|
@@ -151,23 +77,17 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
151
77
|
|
|
152
78
|
it 'handles different output formats correctly' do
|
|
153
79
|
# Test uncovered command with different outputs
|
|
154
|
-
uncovered_output =
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
expect(uncovered_output).to match(/Uncovered lines:\s*2\b/)
|
|
80
|
+
uncovered_output, _err, status = run_cli_with_status(
|
|
81
|
+
'--root', project_root, '--resultset', coverage_dir, 'uncovered', 'lib/foo.rb'
|
|
82
|
+
)
|
|
83
|
+
expect(status).to eq(0)
|
|
84
|
+
expect(uncovered_output).to include('│') # Table format
|
|
162
85
|
|
|
163
86
|
# Test detailed command
|
|
164
|
-
detailed_output =
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
detailed_output = out.string
|
|
169
|
-
end
|
|
170
|
-
|
|
87
|
+
detailed_output, _err, status = run_cli_with_status(
|
|
88
|
+
'--root', project_root, '--resultset', coverage_dir, 'detailed', 'lib/foo.rb'
|
|
89
|
+
)
|
|
90
|
+
expect(status).to eq(0)
|
|
171
91
|
expect(detailed_output).to include('Line', 'Hits', 'Covered')
|
|
172
92
|
end
|
|
173
93
|
end
|
|
@@ -188,7 +108,7 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
188
108
|
server_context: server_context
|
|
189
109
|
)
|
|
190
110
|
|
|
191
|
-
data,
|
|
111
|
+
data, _item = expect_mcp_text_json(summary_response, expected_keys: ['file', 'summary'])
|
|
192
112
|
expect(data['summary']).to include('covered' => 2, 'total' => 3)
|
|
193
113
|
|
|
194
114
|
# Test raw coverage tool
|
|
@@ -199,7 +119,7 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
199
119
|
server_context: server_context
|
|
200
120
|
)
|
|
201
121
|
|
|
202
|
-
raw_data,
|
|
122
|
+
raw_data, _raw_item = expect_mcp_text_json(raw_response, expected_keys: ['file', 'lines'])
|
|
203
123
|
expect(raw_data['lines']).to eq([1, 0, nil, 2])
|
|
204
124
|
|
|
205
125
|
# Test all files tool
|
|
@@ -209,7 +129,7 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
209
129
|
server_context: server_context
|
|
210
130
|
)
|
|
211
131
|
|
|
212
|
-
all_data,
|
|
132
|
+
all_data, = expect_mcp_text_json(all_files_response, expected_keys: ['files', 'counts'])
|
|
213
133
|
expect(all_data['files'].length).to eq(2)
|
|
214
134
|
expect(all_data['counts']['total']).to eq(2)
|
|
215
135
|
end
|
|
@@ -222,7 +142,7 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
222
142
|
resultset: coverage_dir,
|
|
223
143
|
server_context: server_context
|
|
224
144
|
)
|
|
225
|
-
summary_data,
|
|
145
|
+
summary_data, = expect_mcp_text_json(summary_response)
|
|
226
146
|
|
|
227
147
|
# Get data from detailed tool
|
|
228
148
|
detailed_response = SimpleCovMcp::Tools::CoverageDetailedTool.call(
|
|
@@ -231,7 +151,7 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
231
151
|
resultset: coverage_dir,
|
|
232
152
|
server_context: server_context
|
|
233
153
|
)
|
|
234
|
-
detailed_data,
|
|
154
|
+
detailed_data, = expect_mcp_text_json(detailed_response)
|
|
235
155
|
|
|
236
156
|
# Verify consistency between tools
|
|
237
157
|
expect(summary_data['summary']['covered']).to eq(2)
|
|
@@ -257,24 +177,13 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
257
177
|
it 'handles invalid resultset paths gracefully' do
|
|
258
178
|
expect do
|
|
259
179
|
SimpleCovMcp::CoverageModel.new(root: project_root, resultset: '/nonexistent/path')
|
|
260
|
-
end.to raise_error(SimpleCovMcp::
|
|
180
|
+
end.to raise_error(SimpleCovMcp::ResultsetNotFoundError, /Specified resultset not found/)
|
|
261
181
|
end
|
|
262
182
|
|
|
263
183
|
it 'provides helpful CLI error messages' do
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
cli = SimpleCovMcp::CoverageCLI.new
|
|
268
|
-
cli.run([
|
|
269
|
-
'summary', 'lib/nonexistent.rb', '--root', project_root, '--resultset', coverage_dir
|
|
270
|
-
])
|
|
271
|
-
status = 0
|
|
272
|
-
rescue SystemExit => e
|
|
273
|
-
status = e.status
|
|
274
|
-
end
|
|
275
|
-
output = out.string
|
|
276
|
-
error = err.string
|
|
277
|
-
end
|
|
184
|
+
_output, error, status = run_cli_with_status(
|
|
185
|
+
'--root', project_root, '--resultset', coverage_dir, 'summary', 'lib/nonexistent.rb'
|
|
186
|
+
)
|
|
278
187
|
|
|
279
188
|
expect(status).to eq(1)
|
|
280
189
|
expect(error).to include('File error:', 'No coverage entry found')
|
|
@@ -318,7 +227,7 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
318
227
|
let(:default_env) do
|
|
319
228
|
{
|
|
320
229
|
'RUBY_LIB' => lib_path,
|
|
321
|
-
'SIMPLECOV_MCP_OPTS' => "--root #{project_root} --resultset #{coverage_dir}"
|
|
230
|
+
'SIMPLECOV_MCP_OPTS' => "--root #{project_root} --resultset #{coverage_dir} --log-file /dev/null"
|
|
322
231
|
}
|
|
323
232
|
end
|
|
324
233
|
|
|
@@ -332,7 +241,7 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
332
241
|
end
|
|
333
242
|
|
|
334
243
|
# Run the MCP executable with a single JSON-RPC request hash and return the captured streams.
|
|
335
|
-
def run_mcp_json(request_hash, env: default_env, timeout:
|
|
244
|
+
def run_mcp_json(request_hash, env: default_env, timeout: MCP_TIMEOUT)
|
|
336
245
|
Spec::Support::McpRunner.call_json(
|
|
337
246
|
request_hash,
|
|
338
247
|
**runner_args(env: env, timeout: timeout)
|
|
@@ -340,7 +249,7 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
340
249
|
end
|
|
341
250
|
|
|
342
251
|
# Run the MCP executable with a sequence of JSON-RPC requests (one per line).
|
|
343
|
-
def run_mcp_json_stream(request_hashes, env: default_env, timeout:
|
|
252
|
+
def run_mcp_json_stream(request_hashes, env: default_env, timeout: MCP_TIMEOUT)
|
|
344
253
|
Spec::Support::McpRunner.call_json_stream(
|
|
345
254
|
request_hashes,
|
|
346
255
|
**runner_args(env: env, timeout: timeout)
|
|
@@ -348,7 +257,7 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
348
257
|
end
|
|
349
258
|
|
|
350
259
|
# Run the MCP executable with a raw string payload (already encoded as needed).
|
|
351
|
-
def run_mcp_input(input, env: default_env, timeout:
|
|
260
|
+
def run_mcp_input(input, env: default_env, timeout: MCP_TIMEOUT)
|
|
352
261
|
Spec::Support::McpRunner.call(
|
|
353
262
|
input: input,
|
|
354
263
|
**runner_args(env: env, timeout: timeout)
|
|
@@ -357,7 +266,9 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
357
266
|
|
|
358
267
|
def parse_jsonrpc_response(output)
|
|
359
268
|
# MCP server should only write JSON-RPC responses to stdout.
|
|
360
|
-
output.
|
|
269
|
+
# Force UTF-8 encoding to handle any binary data in the output stream.
|
|
270
|
+
safe_output = output.to_s.encode('UTF-8', invalid: :replace, undef: :replace, replace: '')
|
|
271
|
+
safe_output.lines.each do |line|
|
|
361
272
|
stripped = line.strip
|
|
362
273
|
next if stripped.empty?
|
|
363
274
|
|
|
@@ -559,6 +470,37 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
559
470
|
expect(version_text).to match(/SimpleCovMcp version: \d+\.\d+/)
|
|
560
471
|
end
|
|
561
472
|
|
|
473
|
+
it 'executes validate_tool via JSON-RPC' do
|
|
474
|
+
request = {
|
|
475
|
+
jsonrpc: '2.0',
|
|
476
|
+
id: 80,
|
|
477
|
+
method: 'tools/call',
|
|
478
|
+
params: {
|
|
479
|
+
name: 'validate_tool',
|
|
480
|
+
arguments: {
|
|
481
|
+
code: '->(m) { true }',
|
|
482
|
+
root: project_root,
|
|
483
|
+
resultset: coverage_dir
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
stdout = run_mcp_json(request)[:stdout]
|
|
489
|
+
response = parse_jsonrpc_response(stdout)
|
|
490
|
+
|
|
491
|
+
expect(response['id']).to eq(80)
|
|
492
|
+
content = response['result']['content']
|
|
493
|
+
expect(content.first['type']).to eq('text')
|
|
494
|
+
|
|
495
|
+
begin
|
|
496
|
+
result_json = JSON.parse(content.first['text'])
|
|
497
|
+
rescue JSON::ParserError
|
|
498
|
+
puts "DEBUG: Failed to parse JSON. Content was: #{content.first['text']}"
|
|
499
|
+
raise
|
|
500
|
+
end
|
|
501
|
+
expect(result_json).to include('result' => true)
|
|
502
|
+
end
|
|
503
|
+
|
|
562
504
|
it 'handles error responses for invalid tool calls' do
|
|
563
505
|
request = {
|
|
564
506
|
jsonrpc: '2.0',
|
|
@@ -594,7 +536,7 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
594
536
|
malformed_request = "{'jsonrpc': '2.0', 'id': 999, 'method': 'invalid'}"
|
|
595
537
|
|
|
596
538
|
env = { 'RUBY_LIB' => lib_path }
|
|
597
|
-
result = run_mcp_input(malformed_request, env: env
|
|
539
|
+
result = run_mcp_input(malformed_request, env: env)
|
|
598
540
|
|
|
599
541
|
# Should handle gracefully without crashing
|
|
600
542
|
# May return error response or empty output
|
|
@@ -615,8 +557,7 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
615
557
|
|
|
616
558
|
result = run_mcp_json(
|
|
617
559
|
request,
|
|
618
|
-
env: default_env.merge('SIMPLECOV_MCP_OPTS' => '--log-file stderr')
|
|
619
|
-
timeout: 3
|
|
560
|
+
env: default_env.merge('SIMPLECOV_MCP_OPTS' => '--log-file stderr')
|
|
620
561
|
)
|
|
621
562
|
|
|
622
563
|
response = parse_jsonrpc_response(result[:stdout])
|
|
@@ -631,7 +572,7 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
631
572
|
'SIMPLECOV_MCP_OPTS' => '--log-file stdout'
|
|
632
573
|
}
|
|
633
574
|
|
|
634
|
-
result = run_mcp_input(nil, env: env
|
|
575
|
+
result = run_mcp_input(nil, env: env)
|
|
635
576
|
|
|
636
577
|
combined_output = result[:stdout] + result[:stderr]
|
|
637
578
|
expect(combined_output).to include('stdout').and include('not permitted')
|
|
@@ -645,9 +586,12 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
645
586
|
params: { name: 'version_tool', arguments: {} } }
|
|
646
587
|
]
|
|
647
588
|
|
|
648
|
-
result = run_mcp_json_stream(requests
|
|
589
|
+
result = run_mcp_json_stream(requests)
|
|
649
590
|
|
|
650
|
-
|
|
591
|
+
# Force UTF-8 encoding to handle any binary data in the output stream
|
|
592
|
+
safe_stdout = result[:stdout].to_s
|
|
593
|
+
.encode('UTF-8', invalid: :replace, undef: :replace, replace: '')
|
|
594
|
+
responses = safe_stdout.lines.map do |line|
|
|
651
595
|
next if line.strip.empty?
|
|
652
596
|
|
|
653
597
|
begin
|
|
@@ -663,7 +607,7 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
663
607
|
expect(response_ids).to include(100).or include(101)
|
|
664
608
|
end
|
|
665
609
|
|
|
666
|
-
context 'MCP protocol
|
|
610
|
+
context 'when handling MCP protocol errors' do
|
|
667
611
|
it 'returns error for unknown tool name' do
|
|
668
612
|
request = {
|
|
669
613
|
jsonrpc: '2.0',
|
|
@@ -695,7 +639,7 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
695
639
|
text = content.first['text']
|
|
696
640
|
expect(text.downcase).to include('error').or include('not found')
|
|
697
641
|
else
|
|
698
|
-
|
|
642
|
+
raise 'Expected either error or result field in response'
|
|
699
643
|
end
|
|
700
644
|
end
|
|
701
645
|
|
|
@@ -724,7 +668,7 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
724
668
|
text = content.first['text']
|
|
725
669
|
expect(text.downcase).to include('error').or include('required').or include('path')
|
|
726
670
|
else
|
|
727
|
-
|
|
671
|
+
raise 'Expected either error or result field in response'
|
|
728
672
|
end
|
|
729
673
|
end
|
|
730
674
|
|
|
@@ -736,7 +680,7 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
736
680
|
params: {
|
|
737
681
|
name: 'coverage_summary_tool',
|
|
738
682
|
arguments: {
|
|
739
|
-
path:
|
|
683
|
+
path: 12_345, # Should be string, not number
|
|
740
684
|
root: project_root,
|
|
741
685
|
resultset: coverage_dir
|
|
742
686
|
}
|
|
@@ -811,7 +755,7 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
811
755
|
it 'handles completely invalid JSON input' do
|
|
812
756
|
invalid_json = 'this is not JSON at all'
|
|
813
757
|
|
|
814
|
-
result = run_mcp_input(invalid_json, env: default_env
|
|
758
|
+
result = run_mcp_input(invalid_json, env: default_env)
|
|
815
759
|
|
|
816
760
|
# Should not crash with unhandled exception
|
|
817
761
|
combined = result[:stdout] + result[:stderr]
|
|
@@ -825,7 +769,7 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
825
769
|
end
|
|
826
770
|
|
|
827
771
|
it 'handles empty input gracefully' do
|
|
828
|
-
result = run_mcp_input('', env: default_env
|
|
772
|
+
result = run_mcp_input('', env: default_env)
|
|
829
773
|
|
|
830
774
|
# Empty input should be handled without crash
|
|
831
775
|
expect(result[:stderr]).not_to include('NameError')
|
|
@@ -835,7 +779,7 @@ RSpec.describe 'SimpleCov MCP Integration Tests' do
|
|
|
835
779
|
it 'handles partial JSON input' do
|
|
836
780
|
partial_json = '{"jsonrpc": "2.0", "id": 300, "method":'
|
|
837
781
|
|
|
838
|
-
result = run_mcp_input(partial_json, env: default_env
|
|
782
|
+
result = run_mcp_input(partial_json, env: default_env)
|
|
839
783
|
|
|
840
784
|
# Should handle gracefully without crashing
|
|
841
785
|
expect(result[:stderr]).not_to include('uninitialized constant')
|
|
@@ -54,7 +54,7 @@ RSpec.describe 'Logging Fallback Behavior' do
|
|
|
54
54
|
context = SimpleCovMcp.create_context(
|
|
55
55
|
error_handler: SimpleCovMcp::ErrorHandlerFactory.for_mcp_server,
|
|
56
56
|
log_target: '/invalid/path/that/does/not/exist.log',
|
|
57
|
-
mode: :
|
|
57
|
+
mode: :mcp
|
|
58
58
|
)
|
|
59
59
|
|
|
60
60
|
stderr_output = nil
|
|
@@ -115,10 +115,10 @@ RSpec.describe 'Logging Fallback Behavior' do
|
|
|
115
115
|
expect(context.mcp_mode?).to be false
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
-
it 'correctly identifies MCP
|
|
118
|
+
it 'correctly identifies MCP mode' do
|
|
119
119
|
context = SimpleCovMcp.create_context(
|
|
120
120
|
error_handler: SimpleCovMcp::ErrorHandlerFactory.for_mcp_server,
|
|
121
|
-
mode: :
|
|
121
|
+
mode: :mcp
|
|
122
122
|
)
|
|
123
123
|
expect(context.library_mode?).to be false
|
|
124
124
|
expect(context.cli_mode?).to be false
|
|
@@ -5,7 +5,7 @@ require 'spec_helper'
|
|
|
5
5
|
RSpec.describe 'MCP Server Bootstrap' do
|
|
6
6
|
it 'does not crash on startup in non-TTY environments' do
|
|
7
7
|
# Simulate a non-TTY environment, which should trigger MCP mode
|
|
8
|
-
allow(
|
|
8
|
+
allow($stdin).to receive(:tty?).and_return(false)
|
|
9
9
|
|
|
10
10
|
# The server will try to run, but we only need to ensure it gets past
|
|
11
11
|
# the point where the NameError would have occurred. We can mock the
|
data/spec/mcp_server_spec.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'spec_helper'
|
|
4
|
+
require 'support/fake_mcp'
|
|
4
5
|
|
|
5
6
|
RSpec.describe SimpleCovMcp::MCPServer do
|
|
6
7
|
# This spec verifies the MCP server boot path without requiring the real
|
|
@@ -10,55 +11,8 @@ RSpec.describe SimpleCovMcp::MCPServer do
|
|
|
10
11
|
# Prepare fakes for MCP server and transport
|
|
11
12
|
module ::MCP; end unless defined?(::MCP)
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
# `last_instance` accessor is a class-level handle to the most recently
|
|
16
|
-
# instantiated fake. Because the production code constructs the server
|
|
17
|
-
# internally, we can't grab the instance directly; recording the most
|
|
18
|
-
# recent instance lets the test fetch it after `run` completes.
|
|
19
|
-
fake_server_class = Class.new do
|
|
20
|
-
class << self
|
|
21
|
-
# Holds the most recently created fake server instance so tests can
|
|
22
|
-
# inspect it after the code under test performs internal construction.
|
|
23
|
-
attr_accessor :last_instance
|
|
24
|
-
end
|
|
25
|
-
attr_reader :params
|
|
26
|
-
|
|
27
|
-
def initialize(name:, version:, tools:)
|
|
28
|
-
@params = { name: name, version: version, tools: tools }
|
|
29
|
-
self.class.last_instance = self
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# Fake stdio transport records whether `open` was called and the server
|
|
34
|
-
# it was initialized with, to confirm that the server was started. It also
|
|
35
|
-
# exposes a `last_instance` class accessor for the same reason as above:
|
|
36
|
-
# to retrieve the instance created during `run` so we can assert on it.
|
|
37
|
-
fake_transport_class = Class.new do
|
|
38
|
-
class << self
|
|
39
|
-
# Holds the most recently created fake transport instance for later
|
|
40
|
-
# assertions (e.g., that `open` was invoked).
|
|
41
|
-
attr_accessor :last_instance
|
|
42
|
-
end
|
|
43
|
-
attr_reader :server, :opened
|
|
44
|
-
|
|
45
|
-
def initialize(server)
|
|
46
|
-
@server = server
|
|
47
|
-
@opened = false
|
|
48
|
-
self.class.last_instance = self
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def open
|
|
52
|
-
@opened = true
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def opened?
|
|
56
|
-
@opened
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
stub_const('MCP::Server', fake_server_class)
|
|
61
|
-
stub_const('MCP::Server::Transports::StdioTransport', fake_transport_class)
|
|
14
|
+
stub_const('MCP::Server', FakeMCP::Server)
|
|
15
|
+
stub_const('MCP::Server::Transports::StdioTransport', FakeMCP::StdioTransport)
|
|
62
16
|
|
|
63
17
|
server_context = SimpleCovMcp.create_context(
|
|
64
18
|
error_handler: SimpleCovMcp::ErrorHandlerFactory.for_mcp_server,
|
|
@@ -73,8 +27,8 @@ RSpec.describe SimpleCovMcp::MCPServer do
|
|
|
73
27
|
expect(SimpleCovMcp.context).to eq(baseline_context)
|
|
74
28
|
|
|
75
29
|
# Fetch the instances created during `run` via the class-level hooks.
|
|
76
|
-
fake_server =
|
|
77
|
-
fake_transport =
|
|
30
|
+
fake_server = FakeMCP::Server.last_instance
|
|
31
|
+
fake_transport = FakeMCP::StdioTransport.last_instance
|
|
78
32
|
|
|
79
33
|
expect(fake_transport).not_to be_nil
|
|
80
34
|
expect(fake_transport).to be_opened
|
|
@@ -83,7 +37,70 @@ RSpec.describe SimpleCovMcp::MCPServer do
|
|
|
83
37
|
expect(fake_server.params[:name]).to eq('simplecov-mcp')
|
|
84
38
|
# Ensure expected tools are registered
|
|
85
39
|
tool_names = fake_server.params[:tools].map { |t| t.name.split('::').last }
|
|
86
|
-
expect(tool_names).to include(
|
|
87
|
-
'
|
|
40
|
+
expect(tool_names).to include(
|
|
41
|
+
'AllFilesCoverageTool',
|
|
42
|
+
'CoverageDetailedTool',
|
|
43
|
+
'CoverageRawTool',
|
|
44
|
+
'CoverageSummaryTool',
|
|
45
|
+
'CoverageTotalsTool',
|
|
46
|
+
'UncoveredLinesTool',
|
|
47
|
+
'CoverageTableTool',
|
|
48
|
+
'HelpTool',
|
|
49
|
+
'VersionTool'
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
describe 'TOOLSET and TOOL_GUIDE consistency' do
|
|
54
|
+
it 'includes all tools documented in HelpTool TOOL_GUIDE' do
|
|
55
|
+
# Get tool classes from TOOLSET
|
|
56
|
+
toolset_classes = described_class::TOOLSET
|
|
57
|
+
|
|
58
|
+
# Get tool classes from TOOL_GUIDE
|
|
59
|
+
tool_guide_classes = SimpleCovMcp::Tools::HelpTool::TOOL_GUIDE.map { |guide| guide[:tool] }
|
|
60
|
+
|
|
61
|
+
# Every tool in TOOL_GUIDE should be in TOOLSET
|
|
62
|
+
tool_guide_classes.each do |tool_class|
|
|
63
|
+
expect(toolset_classes).to include(tool_class),
|
|
64
|
+
"Expected TOOLSET to include #{tool_class.name}, but it was missing. " \
|
|
65
|
+
'Add it to MCPServer::TOOLSET or remove from HelpTool::TOOL_GUIDE.'
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'has corresponding TOOL_GUIDE entry for all tools (except HelpTool itself)' do
|
|
70
|
+
toolset_classes = described_class::TOOLSET
|
|
71
|
+
tool_guide_classes = SimpleCovMcp::Tools::HelpTool::TOOL_GUIDE.map { |guide| guide[:tool] }
|
|
72
|
+
|
|
73
|
+
# Every tool in TOOLSET should be in TOOL_GUIDE (except HelpTool which documents itself)
|
|
74
|
+
toolset_classes.each do |tool_class|
|
|
75
|
+
# HelpTool doesn't need an entry about itself
|
|
76
|
+
next if tool_class == SimpleCovMcp::Tools::HelpTool
|
|
77
|
+
|
|
78
|
+
expect(tool_guide_classes).to include(tool_class),
|
|
79
|
+
"Expected TOOL_GUIDE to document #{tool_class.name}, but it was missing. " \
|
|
80
|
+
'Add documentation for this tool to HelpTool::TOOL_GUIDE.'
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'registers the expected number of tools' do
|
|
85
|
+
expect(described_class::TOOLSET.length).to eq(10)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it 'registers all tool classes defined in SimpleCovMcp::Tools module' do
|
|
89
|
+
# This test catches the bug where a tool file is created, required in
|
|
90
|
+
# simplecov_mcp.rb, but not added to MCPServer::TOOLSET.
|
|
91
|
+
#
|
|
92
|
+
# Get all classes in the Tools module that inherit from BaseTool
|
|
93
|
+
tool_classes = SimpleCovMcp::Tools.constants
|
|
94
|
+
.map { |const_name| SimpleCovMcp::Tools.const_get(const_name) }
|
|
95
|
+
.select { |const| const.is_a?(Class) && const < SimpleCovMcp::BaseTool }
|
|
96
|
+
|
|
97
|
+
toolset_classes = described_class::TOOLSET
|
|
98
|
+
|
|
99
|
+
tool_classes.each do |tool_class|
|
|
100
|
+
expect(toolset_classes).to include(tool_class),
|
|
101
|
+
"Expected TOOLSET to include #{tool_class.name}, but it was missing. " \
|
|
102
|
+
'The tool class exists in SimpleCovMcp::Tools but is not registered in MCPServer::TOOLSET.'
|
|
103
|
+
end
|
|
104
|
+
end
|
|
88
105
|
end
|
|
89
106
|
end
|