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.
- 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 +23 -0
- data/coverband/log.272267 +1 -0
- data/coverband.gemspec +3 -1
- data/lib/coverband/adapters/hash_redis_store.rb +1 -3
- 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/railtie.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 +7 -7
- data/test/benchmarks/benchmark.rake +7 -15
- 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/track_key_test.rb +9 -9
- 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 +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
|