coverband 6.1.6 → 6.1.7
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/.github/workflows/main.yml +4 -4
- data/README.md +123 -0
- data/agents.md +217 -0
- data/bin/coverband-mcp +42 -0
- data/changes.md +18 -0
- data/coverband/log.272267 +1 -0
- data/coverband.gemspec +3 -1
- data/lib/coverband/collectors/route_tracker.rb +1 -1
- data/lib/coverband/collectors/view_tracker.rb +21 -13
- data/lib/coverband/configuration.rb +57 -18
- data/lib/coverband/integrations/resque.rb +2 -2
- data/lib/coverband/mcp/http_handler.rb +118 -0
- data/lib/coverband/mcp/server.rb +116 -0
- data/lib/coverband/mcp/tools/get_coverage_summary.rb +41 -0
- data/lib/coverband/mcp/tools/get_dead_methods.rb +69 -0
- data/lib/coverband/mcp/tools/get_file_coverage.rb +72 -0
- data/lib/coverband/mcp/tools/get_route_tracker_data.rb +60 -0
- data/lib/coverband/mcp/tools/get_translation_tracker_data.rb +60 -0
- data/lib/coverband/mcp/tools/get_uncovered_files.rb +73 -0
- data/lib/coverband/mcp/tools/get_view_tracker_data.rb +60 -0
- data/lib/coverband/mcp.rb +27 -0
- data/lib/coverband/reporters/base.rb +2 -4
- data/lib/coverband/reporters/web.rb +17 -14
- data/lib/coverband/utils/lines_classifier.rb +1 -1
- data/lib/coverband/utils/result.rb +2 -1
- data/lib/coverband/utils/source_file.rb +5 -5
- data/lib/coverband/utils/tasks.rb +31 -0
- data/lib/coverband/version.rb +1 -1
- data/lib/coverband.rb +2 -2
- data/test/coverband/file_store_integration_test.rb +72 -0
- data/test/coverband/file_store_redis_error_test.rb +56 -0
- data/test/coverband/github_issue_586_test.rb +46 -0
- data/test/coverband/initialization_timing_test.rb +71 -0
- data/test/coverband/mcp/http_handler_test.rb +159 -0
- data/test/coverband/mcp/security_test.rb +145 -0
- data/test/coverband/mcp/server_test.rb +125 -0
- data/test/coverband/mcp/tools/get_coverage_summary_test.rb +75 -0
- data/test/coverband/mcp/tools/get_dead_methods_test.rb +162 -0
- data/test/coverband/mcp/tools/get_file_coverage_test.rb +159 -0
- data/test/coverband/mcp/tools/get_route_tracker_data_test.rb +122 -0
- data/test/coverband/mcp/tools/get_translation_tracker_data_test.rb +122 -0
- data/test/coverband/mcp/tools/get_uncovered_files_test.rb +177 -0
- data/test/coverband/mcp/tools/get_view_tracker_data_test.rb +122 -0
- data/test/coverband/reporters/web_test.rb +5 -0
- data/test/coverband/tracker_initialization_test.rb +75 -0
- data/test/coverband/user_environment_simulation_test.rb +75 -0
- data/test/coverband/utils/lines_classifier_test.rb +1 -1
- data/test/integration/mcp_integration_test.rb +175 -0
- data/test/test_helper.rb +4 -5
- metadata +65 -6
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require File.expand_path("../../../test_helper", File.dirname(__FILE__))
|
|
4
|
+
|
|
5
|
+
begin
|
|
6
|
+
require "coverband/mcp"
|
|
7
|
+
rescue LoadError
|
|
8
|
+
puts "MCP gem not available, skipping MCP tools tests"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
if defined?(Coverband::MCP)
|
|
12
|
+
class GetRouteTrackerDataTest < Minitest::Test
|
|
13
|
+
def setup
|
|
14
|
+
super
|
|
15
|
+
Coverband.configure do |config|
|
|
16
|
+
config.store = Coverband::Adapters::RedisStore.new(Redis.new(db: 2))
|
|
17
|
+
config.track_routes = true
|
|
18
|
+
config.mcp_enabled = true # Enable MCP for testing
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def teardown
|
|
23
|
+
super
|
|
24
|
+
Coverband.configuration.store&.clear!
|
|
25
|
+
Coverband.configuration.track_routes = false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
test "tool has correct metadata" do
|
|
29
|
+
assert_includes Coverband::MCP::Tools::GetRouteTrackerData.description, "Rails route usage tracking"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
test "input schema has optional show_unused_only parameter" do
|
|
33
|
+
schema = Coverband::MCP::Tools::GetRouteTrackerData.input_schema
|
|
34
|
+
assert_instance_of ::MCP::Tool::InputSchema, schema
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
test "call returns route tracking data when tracker is enabled" do
|
|
38
|
+
tracker_mock = mock("route_tracker")
|
|
39
|
+
tracker_mock.expects(:tracking_since).returns("2024-01-01")
|
|
40
|
+
tracker_mock.expects(:as_json).returns({
|
|
41
|
+
"used_keys" => ["GET /users", "POST /users", "GET /users/:id"],
|
|
42
|
+
"unused_keys" => ["DELETE /users/:id", "PATCH /users/:id"]
|
|
43
|
+
}.to_json)
|
|
44
|
+
|
|
45
|
+
Coverband.configuration.expects(:route_tracker).returns(tracker_mock)
|
|
46
|
+
|
|
47
|
+
response = Coverband::MCP::Tools::GetRouteTrackerData.call(server_context: {})
|
|
48
|
+
|
|
49
|
+
assert_instance_of ::MCP::Tool::Response, response
|
|
50
|
+
|
|
51
|
+
result = JSON.parse(response.content.first[:text])
|
|
52
|
+
|
|
53
|
+
assert_equal "2024-01-01", result["tracking_since"]
|
|
54
|
+
assert_equal 3, result["total_used"]
|
|
55
|
+
assert_equal 2, result["total_unused"]
|
|
56
|
+
assert_includes result["used_routes"], "GET /users"
|
|
57
|
+
assert_includes result["unused_routes"], "DELETE /users/:id"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
test "call returns only unused routes when show_unused_only is true" do
|
|
61
|
+
tracker_mock = mock("route_tracker")
|
|
62
|
+
tracker_mock.expects(:tracking_since).returns("2024-01-01")
|
|
63
|
+
tracker_mock.expects(:as_json).returns({
|
|
64
|
+
"unused_keys" => ["DELETE /users/:id", "PATCH /users/:id"]
|
|
65
|
+
}.to_json)
|
|
66
|
+
|
|
67
|
+
Coverband.configuration.expects(:route_tracker).returns(tracker_mock)
|
|
68
|
+
|
|
69
|
+
response = Coverband::MCP::Tools::GetRouteTrackerData.call(
|
|
70
|
+
show_unused_only: true,
|
|
71
|
+
server_context: {}
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
result = JSON.parse(response.content.first[:text])
|
|
75
|
+
|
|
76
|
+
assert_equal "2024-01-01", result["tracking_since"]
|
|
77
|
+
assert_equal 2, result["total_unused"]
|
|
78
|
+
assert_includes result["unused_routes"], "DELETE /users/:id"
|
|
79
|
+
refute_includes result, "used_routes"
|
|
80
|
+
refute_includes result, "total_used"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
test "call returns message when route tracking is not enabled" do
|
|
84
|
+
Coverband.configuration.expects(:route_tracker).returns(nil)
|
|
85
|
+
|
|
86
|
+
response = Coverband::MCP::Tools::GetRouteTrackerData.call(server_context: {})
|
|
87
|
+
|
|
88
|
+
assert_instance_of ::MCP::Tool::Response, response
|
|
89
|
+
assert_includes response.content.first[:text], "Route tracking is not enabled"
|
|
90
|
+
assert_includes response.content.first[:text], "config.track_routes = true"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
test "call handles empty tracking data gracefully" do
|
|
94
|
+
tracker_mock = mock("route_tracker")
|
|
95
|
+
tracker_mock.expects(:tracking_since).returns("2024-01-01")
|
|
96
|
+
tracker_mock.expects(:as_json).returns({
|
|
97
|
+
"used_keys" => nil,
|
|
98
|
+
"unused_keys" => nil
|
|
99
|
+
}.to_json)
|
|
100
|
+
|
|
101
|
+
Coverband.configuration.expects(:route_tracker).returns(tracker_mock)
|
|
102
|
+
|
|
103
|
+
response = Coverband::MCP::Tools::GetRouteTrackerData.call(server_context: {})
|
|
104
|
+
|
|
105
|
+
result = JSON.parse(response.content.first[:text])
|
|
106
|
+
|
|
107
|
+
assert_equal 0, result["total_used"]
|
|
108
|
+
assert_equal 0, result["total_unused"]
|
|
109
|
+
assert_equal [], result["used_routes"]
|
|
110
|
+
assert_equal [], result["unused_routes"]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
test "call handles errors gracefully" do
|
|
114
|
+
Coverband.configuration.expects(:route_tracker).raises(StandardError.new("Test error"))
|
|
115
|
+
|
|
116
|
+
response = Coverband::MCP::Tools::GetRouteTrackerData.call(server_context: {})
|
|
117
|
+
|
|
118
|
+
assert_instance_of ::MCP::Tool::Response, response
|
|
119
|
+
assert_includes response.content.first[:text], "Error getting route tracker data: Test error"
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require File.expand_path("../../../test_helper", File.dirname(__FILE__))
|
|
4
|
+
|
|
5
|
+
begin
|
|
6
|
+
require "coverband/mcp"
|
|
7
|
+
rescue LoadError
|
|
8
|
+
puts "MCP gem not available, skipping MCP tools tests"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
if defined?(Coverband::MCP)
|
|
12
|
+
class GetTranslationTrackerDataTest < Minitest::Test
|
|
13
|
+
def setup
|
|
14
|
+
super
|
|
15
|
+
Coverband.configure do |config|
|
|
16
|
+
config.store = Coverband::Adapters::RedisStore.new(Redis.new(db: 2))
|
|
17
|
+
config.track_translations = true
|
|
18
|
+
config.mcp_enabled = true # Enable MCP for testing
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def teardown
|
|
23
|
+
super
|
|
24
|
+
Coverband.configuration.store&.clear!
|
|
25
|
+
Coverband.configuration.track_translations = false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
test "tool has correct metadata" do
|
|
29
|
+
assert_includes Coverband::MCP::Tools::GetTranslationTrackerData.description, "I18n translation key usage"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
test "input schema has optional show_unused_only parameter" do
|
|
33
|
+
schema = Coverband::MCP::Tools::GetTranslationTrackerData.input_schema
|
|
34
|
+
assert_instance_of ::MCP::Tool::InputSchema, schema
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
test "call returns translation tracking data when tracker is enabled" do
|
|
38
|
+
tracker_mock = mock("translation_tracker")
|
|
39
|
+
tracker_mock.expects(:tracking_since).returns("2024-01-01")
|
|
40
|
+
tracker_mock.expects(:as_json).returns({
|
|
41
|
+
"used_keys" => ["user.name", "user.email", "errors.required"],
|
|
42
|
+
"unused_keys" => ["admin.dashboard", "legacy.message"]
|
|
43
|
+
}.to_json)
|
|
44
|
+
|
|
45
|
+
Coverband.configuration.expects(:translations_tracker).returns(tracker_mock)
|
|
46
|
+
|
|
47
|
+
response = Coverband::MCP::Tools::GetTranslationTrackerData.call(server_context: {})
|
|
48
|
+
|
|
49
|
+
assert_instance_of ::MCP::Tool::Response, response
|
|
50
|
+
|
|
51
|
+
result = JSON.parse(response.content.first[:text])
|
|
52
|
+
|
|
53
|
+
assert_equal "2024-01-01", result["tracking_since"]
|
|
54
|
+
assert_equal 3, result["total_used"]
|
|
55
|
+
assert_equal 2, result["total_unused"]
|
|
56
|
+
assert_includes result["used_translations"], "user.name"
|
|
57
|
+
assert_includes result["unused_translations"], "admin.dashboard"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
test "call returns only unused translations when show_unused_only is true" do
|
|
61
|
+
tracker_mock = mock("translation_tracker")
|
|
62
|
+
tracker_mock.expects(:tracking_since).returns("2024-01-01")
|
|
63
|
+
tracker_mock.expects(:as_json).returns({
|
|
64
|
+
"unused_keys" => ["admin.dashboard", "legacy.message"]
|
|
65
|
+
}.to_json)
|
|
66
|
+
|
|
67
|
+
Coverband.configuration.expects(:translations_tracker).returns(tracker_mock)
|
|
68
|
+
|
|
69
|
+
response = Coverband::MCP::Tools::GetTranslationTrackerData.call(
|
|
70
|
+
show_unused_only: true,
|
|
71
|
+
server_context: {}
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
result = JSON.parse(response.content.first[:text])
|
|
75
|
+
|
|
76
|
+
assert_equal "2024-01-01", result["tracking_since"]
|
|
77
|
+
assert_equal 2, result["total_unused"]
|
|
78
|
+
assert_includes result["unused_translations"], "admin.dashboard"
|
|
79
|
+
refute_includes result, "used_translations"
|
|
80
|
+
refute_includes result, "total_used"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
test "call returns message when translation tracking is not enabled" do
|
|
84
|
+
Coverband.configuration.expects(:translations_tracker).returns(nil)
|
|
85
|
+
|
|
86
|
+
response = Coverband::MCP::Tools::GetTranslationTrackerData.call(server_context: {})
|
|
87
|
+
|
|
88
|
+
assert_instance_of ::MCP::Tool::Response, response
|
|
89
|
+
assert_includes response.content.first[:text], "Translation tracking is not enabled"
|
|
90
|
+
assert_includes response.content.first[:text], "config.track_translations = true"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
test "call handles empty tracking data gracefully" do
|
|
94
|
+
tracker_mock = mock("translation_tracker")
|
|
95
|
+
tracker_mock.expects(:tracking_since).returns("2024-01-01")
|
|
96
|
+
tracker_mock.expects(:as_json).returns({
|
|
97
|
+
"used_keys" => nil,
|
|
98
|
+
"unused_keys" => nil
|
|
99
|
+
}.to_json)
|
|
100
|
+
|
|
101
|
+
Coverband.configuration.expects(:translations_tracker).returns(tracker_mock)
|
|
102
|
+
|
|
103
|
+
response = Coverband::MCP::Tools::GetTranslationTrackerData.call(server_context: {})
|
|
104
|
+
|
|
105
|
+
result = JSON.parse(response.content.first[:text])
|
|
106
|
+
|
|
107
|
+
assert_equal 0, result["total_used"]
|
|
108
|
+
assert_equal 0, result["total_unused"]
|
|
109
|
+
assert_equal [], result["used_translations"]
|
|
110
|
+
assert_equal [], result["unused_translations"]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
test "call handles errors gracefully" do
|
|
114
|
+
Coverband.configuration.expects(:translations_tracker).raises(StandardError.new("Test error"))
|
|
115
|
+
|
|
116
|
+
response = Coverband::MCP::Tools::GetTranslationTrackerData.call(server_context: {})
|
|
117
|
+
|
|
118
|
+
assert_instance_of ::MCP::Tool::Response, response
|
|
119
|
+
assert_includes response.content.first[:text], "Error getting translation tracker data: Test error"
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require File.expand_path("../../../test_helper", File.dirname(__FILE__))
|
|
4
|
+
|
|
5
|
+
begin
|
|
6
|
+
require "coverband/mcp"
|
|
7
|
+
rescue LoadError
|
|
8
|
+
puts "MCP gem not available, skipping MCP tools tests"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
if defined?(Coverband::MCP)
|
|
12
|
+
class GetUncoveredFilesTest < Minitest::Test
|
|
13
|
+
def setup
|
|
14
|
+
super
|
|
15
|
+
Coverband.configure do |config|
|
|
16
|
+
config.store = Coverband::Adapters::RedisStore.new(Redis.new(db: 2))
|
|
17
|
+
config.mcp_enabled = true # Enable MCP for testing
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def teardown
|
|
22
|
+
super
|
|
23
|
+
Coverband.configuration.store&.clear!
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
test "tool has correct metadata" do
|
|
27
|
+
assert_includes Coverband::MCP::Tools::GetUncoveredFiles.description, "coverage below a specified threshold"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
test "input schema has optional parameters" do
|
|
31
|
+
schema = Coverband::MCP::Tools::GetUncoveredFiles.input_schema
|
|
32
|
+
assert_instance_of ::MCP::Tool::InputSchema, schema
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
test "call returns uncovered files below threshold" do
|
|
36
|
+
mock_files = {
|
|
37
|
+
"/app/models/user.rb" => {"covered_percent" => 30.0, "never_loaded" => false},
|
|
38
|
+
"/app/models/order.rb" => {"covered_percent" => 80.0, "never_loaded" => false},
|
|
39
|
+
"/app/helpers/helper.rb" => {"covered_percent" => 20.0, "never_loaded" => false},
|
|
40
|
+
"/app/unused.rb" => {"covered_percent" => 0, "never_loaded" => true}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
mock_data = {"files" => mock_files}
|
|
44
|
+
|
|
45
|
+
report_mock = mock("json_report")
|
|
46
|
+
report_mock.expects(:report).returns(mock_data.to_json)
|
|
47
|
+
Coverband::Reporters::JSONReport.expects(:new).with(
|
|
48
|
+
Coverband.configuration.store,
|
|
49
|
+
line_coverage: false
|
|
50
|
+
).returns(report_mock)
|
|
51
|
+
|
|
52
|
+
response = Coverband::MCP::Tools::GetUncoveredFiles.call(
|
|
53
|
+
threshold: 50,
|
|
54
|
+
server_context: {}
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
assert_instance_of ::MCP::Tool::Response, response
|
|
58
|
+
|
|
59
|
+
result = JSON.parse(response.content.first[:text])
|
|
60
|
+
|
|
61
|
+
# Should include files below 50% and never loaded files
|
|
62
|
+
expected_files = ["/app/helpers/helper.rb", "/app/models/user.rb", "/app/unused.rb"]
|
|
63
|
+
actual_files = result["files"].map { |file| file["file"] }
|
|
64
|
+
|
|
65
|
+
assert_equal 3, result["files"].length
|
|
66
|
+
expected_files.each do |file|
|
|
67
|
+
assert_includes actual_files, file
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Should be sorted by coverage percentage (ascending)
|
|
71
|
+
coverages = result["files"].map { |file| file["covered_percent"] || 0 }
|
|
72
|
+
assert_equal coverages.sort, coverages
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
test "call excludes never loaded files when include_never_loaded is false" do
|
|
76
|
+
mock_files = {
|
|
77
|
+
"/app/models/user.rb" => {"covered_percent" => 30.0, "never_loaded" => false},
|
|
78
|
+
"/app/unused.rb" => {"covered_percent" => 0, "never_loaded" => true}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
mock_data = {"files" => mock_files}
|
|
82
|
+
|
|
83
|
+
report_mock = mock("json_report")
|
|
84
|
+
report_mock.expects(:report).returns(mock_data.to_json)
|
|
85
|
+
Coverband::Reporters::JSONReport.expects(:new).returns(report_mock)
|
|
86
|
+
|
|
87
|
+
response = Coverband::MCP::Tools::GetUncoveredFiles.call(
|
|
88
|
+
threshold: 50,
|
|
89
|
+
include_never_loaded: false,
|
|
90
|
+
server_context: {}
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
result = JSON.parse(response.content.first[:text])
|
|
94
|
+
|
|
95
|
+
# Should only include user.rb (below threshold but not never_loaded)
|
|
96
|
+
assert_equal 1, result["files"].length
|
|
97
|
+
assert_equal "/app/models/user.rb", result["files"].first["file"]
|
|
98
|
+
assert_equal 30.0, result["files"].first["covered_percent"]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
test "call uses default values when parameters not provided" do
|
|
102
|
+
mock_files = {
|
|
103
|
+
"/app/models/user.rb" => {"covered_percent" => 40.0, "never_loaded" => false},
|
|
104
|
+
"/app/models/order.rb" => {"covered_percent" => 60.0, "never_loaded" => false}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
mock_data = {"files" => mock_files}
|
|
108
|
+
|
|
109
|
+
report_mock = mock("json_report")
|
|
110
|
+
report_mock.expects(:report).returns(mock_data.to_json)
|
|
111
|
+
Coverband::Reporters::JSONReport.expects(:new).returns(report_mock)
|
|
112
|
+
|
|
113
|
+
response = Coverband::MCP::Tools::GetUncoveredFiles.call(server_context: {})
|
|
114
|
+
|
|
115
|
+
result = JSON.parse(response.content.first[:text])
|
|
116
|
+
|
|
117
|
+
# Default threshold is 50, so should only include user.rb (40%)
|
|
118
|
+
assert_equal 1, result["files"].length
|
|
119
|
+
assert_equal "/app/models/user.rb", result["files"].first["file"]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
test "call handles files with nil covered_percent" do
|
|
123
|
+
mock_files = {
|
|
124
|
+
"/app/models/user.rb" => {"covered_percent" => nil, "never_loaded" => false},
|
|
125
|
+
"/app/models/order.rb" => {"covered_percent" => 60.0, "never_loaded" => false}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
mock_data = {"files" => mock_files}
|
|
129
|
+
|
|
130
|
+
report_mock = mock("json_report")
|
|
131
|
+
report_mock.expects(:report).returns(mock_data.to_json)
|
|
132
|
+
Coverband::Reporters::JSONReport.expects(:new).returns(report_mock)
|
|
133
|
+
|
|
134
|
+
response = Coverband::MCP::Tools::GetUncoveredFiles.call(
|
|
135
|
+
threshold: 50,
|
|
136
|
+
server_context: {}
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
result = JSON.parse(response.content.first[:text])
|
|
140
|
+
|
|
141
|
+
# File with nil coverage should be included (treated as 0)
|
|
142
|
+
assert_equal 1, result["files"].length
|
|
143
|
+
assert_equal "/app/models/user.rb", result["files"].first["file"]
|
|
144
|
+
assert_nil result["files"].first["covered_percent"]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
test "call returns empty array when no files below threshold" do
|
|
148
|
+
mock_files = {
|
|
149
|
+
"/app/models/user.rb" => {"covered_percent" => 80.0, "never_loaded" => false},
|
|
150
|
+
"/app/models/order.rb" => {"covered_percent" => 90.0, "never_loaded" => false}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
mock_data = {"files" => mock_files}
|
|
154
|
+
|
|
155
|
+
report_mock = mock("json_report")
|
|
156
|
+
report_mock.expects(:report).returns(mock_data.to_json)
|
|
157
|
+
Coverband::Reporters::JSONReport.expects(:new).returns(report_mock)
|
|
158
|
+
|
|
159
|
+
response = Coverband::MCP::Tools::GetUncoveredFiles.call(
|
|
160
|
+
threshold: 50,
|
|
161
|
+
server_context: {}
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
result = JSON.parse(response.content.first[:text])
|
|
165
|
+
assert_equal 0, result["files"].length
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
test "call handles errors gracefully" do
|
|
169
|
+
Coverband::Reporters::JSONReport.expects(:new).raises(StandardError.new("Test error"))
|
|
170
|
+
|
|
171
|
+
response = Coverband::MCP::Tools::GetUncoveredFiles.call(server_context: {})
|
|
172
|
+
|
|
173
|
+
assert_instance_of ::MCP::Tool::Response, response
|
|
174
|
+
assert_includes response.content.first[:text], "Error getting uncovered files: Test error"
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require File.expand_path("../../../test_helper", File.dirname(__FILE__))
|
|
4
|
+
|
|
5
|
+
begin
|
|
6
|
+
require "coverband/mcp"
|
|
7
|
+
rescue LoadError
|
|
8
|
+
puts "MCP gem not available, skipping MCP tools tests"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
if defined?(Coverband::MCP)
|
|
12
|
+
class GetViewTrackerDataTest < Minitest::Test
|
|
13
|
+
def setup
|
|
14
|
+
super
|
|
15
|
+
Coverband.configure do |config|
|
|
16
|
+
config.store = Coverband::Adapters::RedisStore.new(Redis.new(db: 2))
|
|
17
|
+
config.track_views = true
|
|
18
|
+
config.mcp_enabled = true # Enable MCP for testing
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def teardown
|
|
23
|
+
super
|
|
24
|
+
Coverband.configuration.store&.clear!
|
|
25
|
+
Coverband.configuration.track_views = false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
test "tool has correct metadata" do
|
|
29
|
+
assert_includes Coverband::MCP::Tools::GetViewTrackerData.description, "Rails view template usage"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
test "input schema has optional show_unused_only parameter" do
|
|
33
|
+
schema = Coverband::MCP::Tools::GetViewTrackerData.input_schema
|
|
34
|
+
assert_instance_of ::MCP::Tool::InputSchema, schema
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
test "call returns view tracking data when tracker is enabled" do
|
|
38
|
+
tracker_mock = mock("view_tracker")
|
|
39
|
+
tracker_mock.expects(:tracking_since).returns("2024-01-01")
|
|
40
|
+
tracker_mock.expects(:as_json).returns({
|
|
41
|
+
"used_keys" => ["app/views/users/index.html.erb", "app/views/users/show.html.erb"],
|
|
42
|
+
"unused_keys" => ["app/views/users/new.html.erb", "app/views/users/edit.html.erb"]
|
|
43
|
+
}.to_json)
|
|
44
|
+
|
|
45
|
+
Coverband.configuration.expects(:view_tracker).returns(tracker_mock)
|
|
46
|
+
|
|
47
|
+
response = Coverband::MCP::Tools::GetViewTrackerData.call(server_context: {})
|
|
48
|
+
|
|
49
|
+
assert_instance_of ::MCP::Tool::Response, response
|
|
50
|
+
|
|
51
|
+
result = JSON.parse(response.content.first[:text])
|
|
52
|
+
|
|
53
|
+
assert_equal "2024-01-01", result["tracking_since"]
|
|
54
|
+
assert_equal 2, result["total_used"]
|
|
55
|
+
assert_equal 2, result["total_unused"]
|
|
56
|
+
assert_includes result["used_views"], "app/views/users/index.html.erb"
|
|
57
|
+
assert_includes result["unused_views"], "app/views/users/edit.html.erb"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
test "call returns only unused views when show_unused_only is true" do
|
|
61
|
+
tracker_mock = mock("view_tracker")
|
|
62
|
+
tracker_mock.expects(:tracking_since).returns("2024-01-01")
|
|
63
|
+
tracker_mock.expects(:as_json).returns({
|
|
64
|
+
"unused_keys" => ["app/views/users/edit.html.erb", "app/views/orders/index.html.erb"]
|
|
65
|
+
}.to_json)
|
|
66
|
+
|
|
67
|
+
Coverband.configuration.expects(:view_tracker).returns(tracker_mock)
|
|
68
|
+
|
|
69
|
+
response = Coverband::MCP::Tools::GetViewTrackerData.call(
|
|
70
|
+
show_unused_only: true,
|
|
71
|
+
server_context: {}
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
result = JSON.parse(response.content.first[:text])
|
|
75
|
+
|
|
76
|
+
assert_equal "2024-01-01", result["tracking_since"]
|
|
77
|
+
assert_equal 2, result["total_unused"]
|
|
78
|
+
assert_includes result["unused_views"], "app/views/users/edit.html.erb"
|
|
79
|
+
refute_includes result, "used_views"
|
|
80
|
+
refute_includes result, "total_used"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
test "call returns message when view tracking is not enabled" do
|
|
84
|
+
Coverband.configuration.expects(:view_tracker).returns(nil)
|
|
85
|
+
|
|
86
|
+
response = Coverband::MCP::Tools::GetViewTrackerData.call(server_context: {})
|
|
87
|
+
|
|
88
|
+
assert_instance_of ::MCP::Tool::Response, response
|
|
89
|
+
assert_includes response.content.first[:text], "View tracking is not enabled"
|
|
90
|
+
assert_includes response.content.first[:text], "config.track_views = true"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
test "call handles empty tracking data gracefully" do
|
|
94
|
+
tracker_mock = mock("view_tracker")
|
|
95
|
+
tracker_mock.expects(:tracking_since).returns("2024-01-01")
|
|
96
|
+
tracker_mock.expects(:as_json).returns({
|
|
97
|
+
"used_keys" => nil,
|
|
98
|
+
"unused_keys" => nil
|
|
99
|
+
}.to_json)
|
|
100
|
+
|
|
101
|
+
Coverband.configuration.expects(:view_tracker).returns(tracker_mock)
|
|
102
|
+
|
|
103
|
+
response = Coverband::MCP::Tools::GetViewTrackerData.call(server_context: {})
|
|
104
|
+
|
|
105
|
+
result = JSON.parse(response.content.first[:text])
|
|
106
|
+
|
|
107
|
+
assert_equal 0, result["total_used"]
|
|
108
|
+
assert_equal 0, result["total_unused"]
|
|
109
|
+
assert_equal [], result["used_views"]
|
|
110
|
+
assert_equal [], result["unused_views"]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
test "call handles errors gracefully" do
|
|
114
|
+
Coverband.configuration.expects(:view_tracker).raises(StandardError.new("Test error"))
|
|
115
|
+
|
|
116
|
+
response = Coverband::MCP::Tools::GetViewTrackerData.call(server_context: {})
|
|
117
|
+
|
|
118
|
+
assert_instance_of ::MCP::Tool::Response, response
|
|
119
|
+
assert_includes response.content.first[:text], "Error getting view tracker data: Test error"
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require File.expand_path("../test_helper", File.dirname(__FILE__))
|
|
4
|
+
|
|
5
|
+
class TrackerInitializationTest < Minitest::Test
|
|
6
|
+
def setup
|
|
7
|
+
super
|
|
8
|
+
Thread.current[:coverband_instance] = nil
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def teardown
|
|
12
|
+
super
|
|
13
|
+
Thread.current[:coverband_instance] = nil
|
|
14
|
+
Coverband.configure do |config|
|
|
15
|
+
# Reset
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
test "tracker initialization can trigger Redis connection" do
|
|
20
|
+
skip "ViewTracker requires Rails 7+" unless defined?(Rails::VERSION) && Rails::VERSION::STRING.split(".").first.to_i >= 7
|
|
21
|
+
|
|
22
|
+
# Reset everything
|
|
23
|
+
Coverband.configuration.instance_variable_set(:@store, nil)
|
|
24
|
+
Coverband.configuration.instance_variable_set(:@view_tracker, nil)
|
|
25
|
+
|
|
26
|
+
# Mock Redis to see if it gets called when trackers are created
|
|
27
|
+
Redis.expects(:new).raises(Redis::CannotConnectError.new("Connection refused")).once
|
|
28
|
+
|
|
29
|
+
# This could happen during railtie initialization
|
|
30
|
+
# Creating a view tracker when no store is configured yet
|
|
31
|
+
Coverband.configuration.railtie!
|
|
32
|
+
|
|
33
|
+
# Should have fallen back to NullStore due to Redis error
|
|
34
|
+
assert_instance_of Coverband::Adapters::NullStore, Coverband.configuration.store
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
test "tracker initialization after FileStore config works fine" do
|
|
38
|
+
skip "ViewTracker requires Rails 7+" unless defined?(Rails::VERSION) && Rails::VERSION::STRING.split(".").first.to_i >= 7
|
|
39
|
+
|
|
40
|
+
# Configure FileStore first
|
|
41
|
+
Coverband.configure do |config|
|
|
42
|
+
config.store = Coverband::Adapters::FileStore.new("tmp/tracker_test")
|
|
43
|
+
config.track_views = true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Redis should never be attempted
|
|
47
|
+
Redis.expects(:new).never
|
|
48
|
+
|
|
49
|
+
# Creating trackers should use the configured FileStore
|
|
50
|
+
Coverband.configuration.railtie!
|
|
51
|
+
|
|
52
|
+
assert_instance_of Coverband::Adapters::FileStore, Coverband.configuration.store
|
|
53
|
+
assert_instance_of Coverband::Collectors::ViewTracker, Coverband.configuration.view_tracker
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
test "store configuration prevents Redis connection in tracker contexts" do
|
|
57
|
+
# Test the core issue without Rails dependency
|
|
58
|
+
# Configure FileStore first
|
|
59
|
+
Coverband.configure do |config|
|
|
60
|
+
config.store = Coverband::Adapters::FileStore.new("tmp/abstract_tracker_test")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Redis should never be attempted
|
|
64
|
+
Redis.expects(:new).never
|
|
65
|
+
|
|
66
|
+
# This tests that the store getter works correctly
|
|
67
|
+
# when called from tracker initialization contexts
|
|
68
|
+
store = Coverband.configuration.store
|
|
69
|
+
assert_instance_of Coverband::Adapters::FileStore, store
|
|
70
|
+
|
|
71
|
+
# Multiple accesses should return the same cached instance
|
|
72
|
+
store2 = Coverband.configuration.store
|
|
73
|
+
assert_same store, store2
|
|
74
|
+
end
|
|
75
|
+
end
|