coverband 6.1.5 → 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 (55) 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 +23 -0
  7. data/coverband/log.272267 +1 -0
  8. data/coverband.gemspec +3 -1
  9. data/lib/coverband/adapters/hash_redis_store.rb +1 -3
  10. data/lib/coverband/collectors/route_tracker.rb +1 -1
  11. data/lib/coverband/collectors/view_tracker.rb +21 -13
  12. data/lib/coverband/configuration.rb +57 -18
  13. data/lib/coverband/integrations/resque.rb +2 -2
  14. data/lib/coverband/mcp/http_handler.rb +118 -0
  15. data/lib/coverband/mcp/server.rb +116 -0
  16. data/lib/coverband/mcp/tools/get_coverage_summary.rb +41 -0
  17. data/lib/coverband/mcp/tools/get_dead_methods.rb +69 -0
  18. data/lib/coverband/mcp/tools/get_file_coverage.rb +72 -0
  19. data/lib/coverband/mcp/tools/get_route_tracker_data.rb +60 -0
  20. data/lib/coverband/mcp/tools/get_translation_tracker_data.rb +60 -0
  21. data/lib/coverband/mcp/tools/get_uncovered_files.rb +73 -0
  22. data/lib/coverband/mcp/tools/get_view_tracker_data.rb +60 -0
  23. data/lib/coverband/mcp.rb +27 -0
  24. data/lib/coverband/reporters/base.rb +2 -4
  25. data/lib/coverband/reporters/web.rb +17 -14
  26. data/lib/coverband/utils/lines_classifier.rb +1 -1
  27. data/lib/coverband/utils/railtie.rb +1 -1
  28. data/lib/coverband/utils/result.rb +2 -1
  29. data/lib/coverband/utils/source_file.rb +5 -5
  30. data/lib/coverband/utils/tasks.rb +31 -0
  31. data/lib/coverband/version.rb +1 -1
  32. data/lib/coverband.rb +7 -7
  33. data/test/benchmarks/benchmark.rake +7 -15
  34. data/test/coverband/file_store_integration_test.rb +72 -0
  35. data/test/coverband/file_store_redis_error_test.rb +56 -0
  36. data/test/coverband/github_issue_586_test.rb +46 -0
  37. data/test/coverband/initialization_timing_test.rb +71 -0
  38. data/test/coverband/mcp/http_handler_test.rb +159 -0
  39. data/test/coverband/mcp/security_test.rb +145 -0
  40. data/test/coverband/mcp/server_test.rb +125 -0
  41. data/test/coverband/mcp/tools/get_coverage_summary_test.rb +75 -0
  42. data/test/coverband/mcp/tools/get_dead_methods_test.rb +162 -0
  43. data/test/coverband/mcp/tools/get_file_coverage_test.rb +159 -0
  44. data/test/coverband/mcp/tools/get_route_tracker_data_test.rb +122 -0
  45. data/test/coverband/mcp/tools/get_translation_tracker_data_test.rb +122 -0
  46. data/test/coverband/mcp/tools/get_uncovered_files_test.rb +177 -0
  47. data/test/coverband/mcp/tools/get_view_tracker_data_test.rb +122 -0
  48. data/test/coverband/reporters/web_test.rb +5 -0
  49. data/test/coverband/track_key_test.rb +9 -9
  50. data/test/coverband/tracker_initialization_test.rb +75 -0
  51. data/test/coverband/user_environment_simulation_test.rb +75 -0
  52. data/test/coverband/utils/lines_classifier_test.rb +1 -1
  53. data/test/integration/mcp_integration_test.rb +175 -0
  54. data/test/test_helper.rb +4 -5
  55. metadata +67 -11
@@ -0,0 +1,162 @@
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 GetDeadMethodsTest < 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::GetDeadMethods.description, "methods that have never been executed"
28
+ end
29
+
30
+ test "input schema has optional file_pattern parameter" do
31
+ schema = Coverband::MCP::Tools::GetDeadMethods.input_schema
32
+ assert_instance_of ::MCP::Tool::InputSchema, schema
33
+ end
34
+
35
+ if defined?(RubyVM::AbstractSyntaxTree)
36
+ test "call returns dead methods when AST support available" do
37
+ mock_dead_methods = [
38
+ {
39
+ file_path: "/app/models/user.rb",
40
+ class_name: "User",
41
+ method_name: "unused_method",
42
+ line_number: 10
43
+ },
44
+ {
45
+ file_path: "/app/models/user.rb",
46
+ class_name: "User",
47
+ method_name: "another_unused",
48
+ line_number: 15
49
+ },
50
+ {
51
+ file_path: "/app/models/order.rb",
52
+ class_name: "Order",
53
+ method_name: "dead_method",
54
+ line_number: 20
55
+ }
56
+ ]
57
+
58
+ Coverband::Utils::DeadMethods.expects(:scan_all).returns(mock_dead_methods)
59
+
60
+ response = Coverband::MCP::Tools::GetDeadMethods.call(server_context: {})
61
+
62
+ assert_instance_of ::MCP::Tool::Response, response
63
+
64
+ result = JSON.parse(response.content.first[:text])
65
+
66
+ assert_equal 3, result["total_dead_methods"]
67
+ assert_equal 2, result["files_with_dead_methods"]
68
+ assert_nil result["file_pattern"]
69
+
70
+ # Check grouped results
71
+ user_file = result["results"].find { |f| f["file"] == "/app/models/user.rb" }
72
+ assert_equal 2, user_file["dead_methods"].length
73
+
74
+ order_file = result["results"].find { |f| f["file"] == "/app/models/order.rb" }
75
+ assert_equal 1, order_file["dead_methods"].length
76
+
77
+ # Check method details
78
+ user_method = user_file["dead_methods"].first
79
+ assert_equal "User", user_method["class_name"]
80
+ assert_equal "unused_method", user_method["method_name"]
81
+ assert_equal 10, user_method["line_number"]
82
+ end
83
+
84
+ test "call filters by file_pattern when provided" do
85
+ mock_dead_methods = [
86
+ {
87
+ file_path: "/app/models/user.rb",
88
+ class_name: "User",
89
+ method_name: "unused_method",
90
+ line_number: 10
91
+ },
92
+ {
93
+ file_path: "/app/helpers/user_helper.rb",
94
+ class_name: "UserHelper",
95
+ method_name: "dead_helper",
96
+ line_number: 5
97
+ }
98
+ ]
99
+
100
+ Coverband::Utils::DeadMethods.expects(:scan_all).returns(mock_dead_methods)
101
+
102
+ response = Coverband::MCP::Tools::GetDeadMethods.call(
103
+ file_pattern: "/app/models/**/*.rb",
104
+ server_context: {}
105
+ )
106
+
107
+ result = JSON.parse(response.content.first[:text])
108
+
109
+ # Should only include the models file
110
+ assert_equal 1, result["total_dead_methods"]
111
+ assert_equal 1, result["files_with_dead_methods"]
112
+ assert_equal "/app/models/**/*.rb", result["file_pattern"]
113
+
114
+ assert_equal 1, result["results"].length
115
+ assert_equal "/app/models/user.rb", result["results"].first["file"]
116
+ end
117
+
118
+ test "call handles no dead methods found" do
119
+ Coverband::Utils::DeadMethods.expects(:scan_all).returns([])
120
+
121
+ response = Coverband::MCP::Tools::GetDeadMethods.call(server_context: {})
122
+
123
+ result = JSON.parse(response.content.first[:text])
124
+
125
+ assert_equal 0, result["total_dead_methods"]
126
+ assert_equal 0, result["files_with_dead_methods"]
127
+ assert_empty result["results"]
128
+ end
129
+ end
130
+
131
+ test "call returns error when AST support not available" do
132
+ # Temporarily hide the constant
133
+ if defined?(RubyVM::AbstractSyntaxTree)
134
+ original_ast = RubyVM::AbstractSyntaxTree
135
+ RubyVM.send(:remove_const, :AbstractSyntaxTree)
136
+ end
137
+
138
+ begin
139
+ response = Coverband::MCP::Tools::GetDeadMethods.call(server_context: {})
140
+
141
+ assert_instance_of ::MCP::Tool::Response, response
142
+ assert_includes response.content.first[:text], "requires Ruby 2.6+ with RubyVM::AbstractSyntaxTree"
143
+ ensure
144
+ # Restore the constant if it was defined
145
+ if defined?(original_ast)
146
+ RubyVM.const_set(:AbstractSyntaxTree, original_ast)
147
+ end
148
+ end
149
+ end
150
+
151
+ test "call handles errors gracefully" do
152
+ if defined?(RubyVM::AbstractSyntaxTree)
153
+ Coverband::Utils::DeadMethods.expects(:scan_all).raises(StandardError.new("Test error"))
154
+
155
+ response = Coverband::MCP::Tools::GetDeadMethods.call(server_context: {})
156
+
157
+ assert_instance_of ::MCP::Tool::Response, response
158
+ assert_includes response.content.first[:text], "Error analyzing dead methods: Test error"
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,159 @@
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 GetFileCoverageTest < 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::GetFileCoverage.description, "line-by-line coverage data"
28
+ end
29
+
30
+ test "input schema requires filename parameter" do
31
+ schema = Coverband::MCP::Tools::GetFileCoverage.input_schema
32
+ assert_instance_of ::MCP::Tool::InputSchema, schema
33
+ end
34
+
35
+ test "call returns file coverage data when file exists" do
36
+ filename = "app/models/user.rb"
37
+ full_path = "/app/app/models/user.rb"
38
+
39
+ mock_file_data = {
40
+ "filename" => full_path,
41
+ "covered_percent" => 85.0,
42
+ "lines_of_code" => 100,
43
+ "lines_covered" => 85,
44
+ "lines_missed" => 15,
45
+ "runtime_percentage" => 90.0,
46
+ "never_loaded" => false,
47
+ "coverage" => [1, 1, 0, 1, 1, nil, 1]
48
+ }
49
+
50
+ mock_data = {
51
+ "files" => {
52
+ full_path => mock_file_data
53
+ }
54
+ }
55
+
56
+ report_mock = mock("json_report")
57
+ report_mock.expects(:report).returns(mock_data.to_json)
58
+ Coverband::Reporters::JSONReport.expects(:new).with(
59
+ Coverband.configuration.store,
60
+ {filename: filename, line_coverage: true}
61
+ ).returns(report_mock)
62
+
63
+ response = Coverband::MCP::Tools::GetFileCoverage.call(
64
+ filename: filename,
65
+ server_context: {}
66
+ )
67
+
68
+ assert_instance_of ::MCP::Tool::Response, response
69
+
70
+ result = JSON.parse(response.content.first[:text])
71
+ assert_includes result, full_path
72
+ assert_equal 85.0, result[full_path]["covered_percent"]
73
+ assert_equal 100, result[full_path]["lines_of_code"]
74
+ assert_equal [1, 1, 0, 1, 1, nil, 1], result[full_path]["coverage"]
75
+ end
76
+
77
+ test "call returns message when no files found" do
78
+ filename = "nonexistent.rb"
79
+
80
+ mock_data = {"files" => nil}
81
+
82
+ report_mock = mock("json_report")
83
+ report_mock.expects(:report).returns(mock_data.to_json)
84
+ Coverband::Reporters::JSONReport.expects(:new).returns(report_mock)
85
+
86
+ response = Coverband::MCP::Tools::GetFileCoverage.call(
87
+ filename: filename,
88
+ server_context: {}
89
+ )
90
+
91
+ assert_instance_of ::MCP::Tool::Response, response
92
+ assert_includes response.content.first[:text], "No coverage data found for file: nonexistent.rb"
93
+ end
94
+
95
+ test "call returns message when no matching files" do
96
+ filename = "nonexistent.rb"
97
+
98
+ mock_data = {
99
+ "files" => {
100
+ "/app/other/file.rb" => {"filename" => "/app/other/file.rb"}
101
+ }
102
+ }
103
+
104
+ report_mock = mock("json_report")
105
+ report_mock.expects(:report).returns(mock_data.to_json)
106
+ Coverband::Reporters::JSONReport.expects(:new).returns(report_mock)
107
+
108
+ response = Coverband::MCP::Tools::GetFileCoverage.call(
109
+ filename: filename,
110
+ server_context: {}
111
+ )
112
+
113
+ assert_instance_of ::MCP::Tool::Response, response
114
+ assert_includes response.content.first[:text], "No coverage data found for file matching: nonexistent.rb"
115
+ end
116
+
117
+ test "call handles partial filename matches" do
118
+ filename = "user"
119
+
120
+ matching_files = {
121
+ "/app/models/user.rb" => {"filename" => "/app/models/user.rb", "covered_percent" => 85.0},
122
+ "/app/helpers/user_helper.rb" => {"filename" => "/app/helpers/user_helper.rb", "covered_percent" => 90.0}
123
+ }
124
+
125
+ mock_data = {
126
+ "files" => matching_files.merge({
127
+ "/app/models/order.rb" => {"filename" => "/app/models/order.rb", "covered_percent" => 75.0}
128
+ })
129
+ }
130
+
131
+ report_mock = mock("json_report")
132
+ report_mock.expects(:report).returns(mock_data.to_json)
133
+ Coverband::Reporters::JSONReport.expects(:new).returns(report_mock)
134
+
135
+ response = Coverband::MCP::Tools::GetFileCoverage.call(
136
+ filename: filename,
137
+ server_context: {}
138
+ )
139
+
140
+ result = JSON.parse(response.content.first[:text])
141
+ assert_equal 2, result.keys.length
142
+ assert_includes result, "/app/models/user.rb"
143
+ assert_includes result, "/app/helpers/user_helper.rb"
144
+ refute_includes result, "/app/models/order.rb"
145
+ end
146
+
147
+ test "call handles errors gracefully" do
148
+ Coverband::Reporters::JSONReport.expects(:new).raises(StandardError.new("Test error"))
149
+
150
+ response = Coverband::MCP::Tools::GetFileCoverage.call(
151
+ filename: "test.rb",
152
+ server_context: {}
153
+ )
154
+
155
+ assert_instance_of ::MCP::Tool::Response, response
156
+ assert_includes response.content.first[:text], "Error getting file coverage: Test error"
157
+ end
158
+ end
159
+ 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 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