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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +4 -4
  3. data/README.md +123 -0
  4. data/agents.md +217 -0
  5. data/bin/coverband-mcp +42 -0
  6. data/changes.md +18 -0
  7. data/coverband/log.272267 +1 -0
  8. data/coverband.gemspec +3 -1
  9. data/lib/coverband/collectors/route_tracker.rb +1 -1
  10. data/lib/coverband/collectors/view_tracker.rb +21 -13
  11. data/lib/coverband/configuration.rb +57 -18
  12. data/lib/coverband/integrations/resque.rb +2 -2
  13. data/lib/coverband/mcp/http_handler.rb +118 -0
  14. data/lib/coverband/mcp/server.rb +116 -0
  15. data/lib/coverband/mcp/tools/get_coverage_summary.rb +41 -0
  16. data/lib/coverband/mcp/tools/get_dead_methods.rb +69 -0
  17. data/lib/coverband/mcp/tools/get_file_coverage.rb +72 -0
  18. data/lib/coverband/mcp/tools/get_route_tracker_data.rb +60 -0
  19. data/lib/coverband/mcp/tools/get_translation_tracker_data.rb +60 -0
  20. data/lib/coverband/mcp/tools/get_uncovered_files.rb +73 -0
  21. data/lib/coverband/mcp/tools/get_view_tracker_data.rb +60 -0
  22. data/lib/coverband/mcp.rb +27 -0
  23. data/lib/coverband/reporters/base.rb +2 -4
  24. data/lib/coverband/reporters/web.rb +17 -14
  25. data/lib/coverband/utils/lines_classifier.rb +1 -1
  26. data/lib/coverband/utils/result.rb +2 -1
  27. data/lib/coverband/utils/source_file.rb +5 -5
  28. data/lib/coverband/utils/tasks.rb +31 -0
  29. data/lib/coverband/version.rb +1 -1
  30. data/lib/coverband.rb +2 -2
  31. data/test/coverband/file_store_integration_test.rb +72 -0
  32. data/test/coverband/file_store_redis_error_test.rb +56 -0
  33. data/test/coverband/github_issue_586_test.rb +46 -0
  34. data/test/coverband/initialization_timing_test.rb +71 -0
  35. data/test/coverband/mcp/http_handler_test.rb +159 -0
  36. data/test/coverband/mcp/security_test.rb +145 -0
  37. data/test/coverband/mcp/server_test.rb +125 -0
  38. data/test/coverband/mcp/tools/get_coverage_summary_test.rb +75 -0
  39. data/test/coverband/mcp/tools/get_dead_methods_test.rb +162 -0
  40. data/test/coverband/mcp/tools/get_file_coverage_test.rb +159 -0
  41. data/test/coverband/mcp/tools/get_route_tracker_data_test.rb +122 -0
  42. data/test/coverband/mcp/tools/get_translation_tracker_data_test.rb +122 -0
  43. data/test/coverband/mcp/tools/get_uncovered_files_test.rb +177 -0
  44. data/test/coverband/mcp/tools/get_view_tracker_data_test.rb +122 -0
  45. data/test/coverband/reporters/web_test.rb +5 -0
  46. data/test/coverband/tracker_initialization_test.rb +75 -0
  47. data/test/coverband/user_environment_simulation_test.rb +75 -0
  48. data/test/coverband/utils/lines_classifier_test.rb +1 -1
  49. data/test/integration/mcp_integration_test.rb +175 -0
  50. data/test/test_helper.rb +4 -5
  51. 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
@@ -40,6 +40,11 @@ module Coverband
40
40
  post "/clear"
41
41
  assert_equal 302, last_response.status
42
42
  end
43
+
44
+ test "json endpoint accepts line_coverage parameter" do
45
+ get "/json?line_coverage=true"
46
+ assert last_response.ok?
47
+ end
43
48
  end
44
49
  end
45
50
 
@@ -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