route_mechanic 0.1.6 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +43 -16
- data/lib/route_mechanic/rspec/matchers.rb +10 -39
- data/lib/route_mechanic/rspec/matchers/base_matcher.rb +42 -0
- data/lib/route_mechanic/rspec/matchers/have_no_unused_actions.rb +21 -0
- data/lib/route_mechanic/rspec/matchers/have_no_unused_routes.rb +21 -0
- data/lib/route_mechanic/rspec/matchers/have_valid_routes.rb +21 -0
- data/lib/route_mechanic/testing/error_aggregator.rb +16 -11
- data/lib/route_mechanic/testing/error_inspector.rb +8 -9
- data/lib/route_mechanic/testing/methods.rb +24 -3
- data/lib/route_mechanic/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 21e6f6fc80509ba7ce1b98ea9e5cc7f3868b83fde1d287d5d35867802bf73b1d
|
4
|
+
data.tar.gz: '094b2771b33125d34fe4eaa06bf77bc4ba0d6d250fe094b2b68c2794e2a0274c'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 51fc1082c4c0857485b6d518f40c1cf8539f8509c38c9311bf62b54b0b5875e62358fc990d10aa4c26b8e0b9b9309f71e330d8236c99f4e478b002019184968b
|
7
|
+
data.tar.gz: 13f6e7e2bd77520b1be677d39609c56a68fe5d87ce7eacb70faaca58a310cceeb669f09cf64d3eea071b36dcbf5f42c6886c08b06f4cc30adb148c8ecde47dcf
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
[](https://badge.fury.io/rb/route_mechanic)
|
3
3
|
[](https://github.com/ohbarye/route_mechanic/actions?query=workflow%3Atest)
|
4
4
|
|
5
|
-
No need to maintain Rails' routing tests manually. RouteMechanic automatically detects broken routes and missing action methods in
|
5
|
+
No need to maintain Rails' routing tests manually. RouteMechanic automatically detects broken routes and missing action methods in controllers once you've finished installation.
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
@@ -24,11 +24,16 @@ $ bundle install
|
|
24
24
|
|
25
25
|
RouteMechanic is available for both RSpec and MiniTest.
|
26
26
|
|
27
|
-
All you have to do is to add just one test case that keeps your application's routes not broken.
|
27
|
+
All you have to do is to add just one test case that keeps your application's routes not broken. Then, RouteMechanic will get to report 2 types of broken routes.
|
28
|
+
|
29
|
+
1. Unused actions
|
30
|
+
- Your application has the controller and the action method but `config/routes.rb` doesn't have corresponding settings.
|
31
|
+
2. Unused routes
|
32
|
+
- Your application's `config/routes.rb` has a routing declaration but no controller has a corresponding action method.
|
28
33
|
|
29
34
|
### RSpec
|
30
35
|
|
31
|
-
Just add
|
36
|
+
Just add one test file that has only one test case using `have_valid_routes` matcher.
|
32
37
|
|
33
38
|
```ruby
|
34
39
|
RSpec.describe 'Rails.application', type: :routing do
|
@@ -38,9 +43,23 @@ RSpec.describe 'Rails.application', type: :routing do
|
|
38
43
|
end
|
39
44
|
```
|
40
45
|
|
46
|
+
If you'd like to test unused actions and unused routes separately or test only one of them, there're matchers to do so.
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
RSpec.describe 'Rails.application', type: :routing do
|
50
|
+
it "fails if application has unused actions" do
|
51
|
+
expect(Rails.application).to have_no_unused_actions
|
52
|
+
end
|
53
|
+
|
54
|
+
it "fails if application has unused routes" do
|
55
|
+
expect(Rails.application).to have_no_unused_routes
|
56
|
+
end
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
41
60
|
### MiniTest
|
42
61
|
|
43
|
-
Just add
|
62
|
+
Just add one test file like below.
|
44
63
|
|
45
64
|
```ruby
|
46
65
|
class RoutingTest < Minitest::Test
|
@@ -52,6 +71,22 @@ class RoutingTest < Minitest::Test
|
|
52
71
|
end
|
53
72
|
```
|
54
73
|
|
74
|
+
If you'd like to test unused actions and unused routes separately or test only one of them, there're assertions to do so.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
class RoutingTest < Minitest::Test
|
78
|
+
include ::RouteMechanic::Testing::Methods
|
79
|
+
|
80
|
+
def test_that_application_has_no_unused_actions
|
81
|
+
assert_no_unused_actions
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_that_application_has_no_unused_routes
|
85
|
+
assert_no_unused_routes
|
86
|
+
end
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
55
90
|
### What if RouteMechanic detects broken routes?
|
56
91
|
|
57
92
|
It tells you broken routes as follows.
|
@@ -72,31 +107,23 @@ It tells you broken routes as follows.
|
|
72
107
|
1 examples, 1 failure, 0 passed
|
73
108
|
```
|
74
109
|
|
75
|
-
RouteMechanic reports 2 types of broken routes.
|
76
|
-
|
77
|
-
1. Missing routes
|
78
|
-
- Your application has the controller and the action method but `config/routes.rb` doesn't have corresponds settings.
|
79
|
-
2. Missing action methods
|
80
|
-
- Your application's `config/routes.rb` has routing declaration but no controller has a correspond action method.
|
81
|
-
|
82
110
|
## Motivation
|
83
111
|
|
84
|
-
I believe most Rails developers write request specs instead of routing specs, and you might wonder what's worth to automate routing
|
112
|
+
I believe most Rails developers write request specs instead of routing specs, and you might wonder what's worth to automate routing specs. Having said that, I can come up with some use-cases of this gem.
|
85
113
|
|
86
114
|
1. When your project is kinda aged and none knows which route is alive and which one is dead.
|
87
115
|
- => You can detect dead code by using this gem.
|
88
116
|
2. When your application doesn't have enough request specs (even controller specs).
|
89
117
|
- => This gem could be a good start point to increase tests to ensure routing is valid.
|
90
|
-
3. When you try to make big refactor of `config/routes.rb`.
|
91
|
-
- => It's burden to run all request specs during refactoring. This could save your time.
|
118
|
+
3. When you try to make a big refactor of `config/routes.rb`.
|
119
|
+
- => It's a burden to run all request specs during refactoring. This could save your time.
|
92
120
|
4. When you're compelled to write routing specs by any pressure. ;-)
|
93
121
|
- => Set you free from tedious work!
|
94
122
|
|
95
|
-
|
96
123
|
## License
|
97
124
|
|
98
125
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
99
126
|
|
100
127
|
## Code of Conduct
|
101
128
|
|
102
|
-
Everyone interacting in the RouteMechanic project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/route_mechanic/blob/master/CODE_OF_CONDUCT.md).
|
129
|
+
Everyone interacting in the RouteMechanic project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/route_mechanic/blob/master/CODE_OF_CONDUCT.md).
|
@@ -1,49 +1,20 @@
|
|
1
|
-
require 'route_mechanic/
|
2
|
-
require 'rspec/matchers/
|
1
|
+
require 'route_mechanic/rspec/matchers/have_valid_routes'
|
2
|
+
require 'route_mechanic/rspec/matchers/have_no_unused_actions'
|
3
|
+
require 'route_mechanic/rspec/matchers/have_no_unused_routes'
|
3
4
|
|
4
5
|
module RouteMechanic
|
5
6
|
module RSpec
|
6
7
|
module Matchers
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
# @param [Rails::Application] expected
|
12
|
-
def initialize(expected)
|
13
|
-
@expected = expected
|
14
|
-
end
|
15
|
-
|
16
|
-
def matches?(_actual)
|
17
|
-
# assert_recognizes does not consider ActionController::RoutingError an
|
18
|
-
# assertion failure, so we have to capture that and Assertion here.
|
19
|
-
match_unless_raises Minitest::Assertion, ActiveSupport::TestCase::Assertion, ActionController::RoutingError do
|
20
|
-
assert_all_routes(@expected)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def failure_message
|
25
|
-
@rescued_exception.message
|
26
|
-
end
|
27
|
-
|
28
|
-
def description
|
29
|
-
"have valid routes"
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
8
|
+
def have_valid_routes(application=Rails.application)
|
9
|
+
HaveValidRoutes.new(application)
|
10
|
+
end
|
33
11
|
|
34
|
-
|
35
|
-
|
36
|
-
begin
|
37
|
-
yield
|
38
|
-
true
|
39
|
-
rescue *exceptions => @rescued_exception
|
40
|
-
false
|
41
|
-
end
|
42
|
-
end
|
12
|
+
def have_no_unused_actions(application=Rails.application)
|
13
|
+
HaveNoUnusedActions.new(application)
|
43
14
|
end
|
44
15
|
|
45
|
-
def
|
46
|
-
|
16
|
+
def have_no_unused_routes(application=Rails.application)
|
17
|
+
HaveNoUnusedRoutes.new(application)
|
47
18
|
end
|
48
19
|
end
|
49
20
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'route_mechanic/testing/methods'
|
2
|
+
require 'rspec/matchers/composable'
|
3
|
+
|
4
|
+
module RouteMechanic
|
5
|
+
module RSpec
|
6
|
+
module Matchers
|
7
|
+
class BaseMatcher
|
8
|
+
include ::RSpec::Matchers::Composable
|
9
|
+
include RouteMechanic::Testing::Methods
|
10
|
+
|
11
|
+
# @param [Rails::Application] expected
|
12
|
+
def initialize(expected)
|
13
|
+
@expected = expected
|
14
|
+
end
|
15
|
+
|
16
|
+
def matches?(_actual)
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
|
20
|
+
def failure_message
|
21
|
+
@rescued_exception.message
|
22
|
+
end
|
23
|
+
|
24
|
+
def description
|
25
|
+
raise NotImplementedError
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def match_unless_raises(*exceptions)
|
31
|
+
exceptions.unshift Exception if exceptions.empty?
|
32
|
+
begin
|
33
|
+
yield
|
34
|
+
true
|
35
|
+
rescue *exceptions => @rescued_exception
|
36
|
+
false
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'route_mechanic/rspec/matchers/base_matcher'
|
2
|
+
|
3
|
+
module RouteMechanic
|
4
|
+
module RSpec
|
5
|
+
module Matchers
|
6
|
+
class HaveNoUnusedActions < BaseMatcher
|
7
|
+
def matches?(_actual)
|
8
|
+
# assert_recognizes does not consider ActionController::RoutingError an
|
9
|
+
# assertion failure, so we have to capture that and Assertion here.
|
10
|
+
match_unless_raises Minitest::Assertion, ActiveSupport::TestCase::Assertion, ActionController::RoutingError do
|
11
|
+
assert_no_unused_actions(@expected)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def description
|
16
|
+
"have no unused actions"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'route_mechanic/rspec/matchers/base_matcher'
|
2
|
+
|
3
|
+
module RouteMechanic
|
4
|
+
module RSpec
|
5
|
+
module Matchers
|
6
|
+
class HaveNoUnusedRoutes < BaseMatcher
|
7
|
+
def matches?(_actual)
|
8
|
+
# assert_recognizes does not consider ActionController::RoutingError an
|
9
|
+
# assertion failure, so we have to capture that and Assertion here.
|
10
|
+
match_unless_raises Minitest::Assertion, ActiveSupport::TestCase::Assertion, ActionController::RoutingError do
|
11
|
+
assert_no_unused_routes(@expected)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def description
|
16
|
+
"have no unused routes"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'route_mechanic/rspec/matchers/base_matcher'
|
2
|
+
|
3
|
+
module RouteMechanic
|
4
|
+
module RSpec
|
5
|
+
module Matchers
|
6
|
+
class HaveValidRoutes < BaseMatcher
|
7
|
+
def matches?(_actual)
|
8
|
+
# assert_recognizes does not consider ActionController::RoutingError an
|
9
|
+
# assertion failure, so we have to capture that and Assertion here.
|
10
|
+
match_unless_raises Minitest::Assertion, ActiveSupport::TestCase::Assertion, ActionController::RoutingError do
|
11
|
+
assert_all_routes(@expected)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def description
|
16
|
+
"have valid routes"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -3,7 +3,7 @@ require "route_mechanic/testing/error_inspector"
|
|
3
3
|
module RouteMechanic
|
4
4
|
module Testing
|
5
5
|
class ErrorAggregator
|
6
|
-
attr_reader :
|
6
|
+
attr_reader :unused_actions_errors, :unused_routes_errors
|
7
7
|
|
8
8
|
# @param [Array<ActionDispatch::Journey::Route>] routes
|
9
9
|
# @param [Array<Controller>] controllers
|
@@ -12,31 +12,36 @@ module RouteMechanic
|
|
12
12
|
@controllers = controllers
|
13
13
|
@config_routes = []
|
14
14
|
@controller_routes = []
|
15
|
-
@
|
16
|
-
@
|
15
|
+
@unused_routes_errors = []
|
16
|
+
@unused_actions_errors = []
|
17
17
|
end
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
# @param [Boolean] unused_actions
|
20
|
+
# @param [Boolean] unused_routes
|
21
|
+
def aggregate(unused_actions: true, unused_routes: true)
|
22
|
+
collect_unused_actions_errors(unused_actions)
|
23
|
+
collect_unused_routes_errors(unused_routes)
|
22
24
|
self
|
23
25
|
end
|
24
26
|
|
27
|
+
# @return [Array<ActionDispatch::Journey::Route>]
|
25
28
|
def all_routes
|
26
29
|
@config_routes + @controller_routes
|
27
30
|
end
|
28
31
|
|
32
|
+
# @return [Boolean]
|
29
33
|
def no_error?
|
30
|
-
[@
|
34
|
+
[@unused_routes_errors, @unused_actions_errors].all?(&:empty?)
|
31
35
|
end
|
32
36
|
|
37
|
+
# @return [String]
|
33
38
|
def error_message
|
34
39
|
ErrorInspector.new(self).message
|
35
40
|
end
|
36
41
|
|
37
42
|
private
|
38
43
|
|
39
|
-
def
|
44
|
+
def collect_unused_actions_errors(report_error)
|
40
45
|
@controllers.each do |controller|
|
41
46
|
controller_path = controller.controller_path
|
42
47
|
controller.action_methods.each do |action_method|
|
@@ -45,7 +50,7 @@ module RouteMechanic
|
|
45
50
|
end
|
46
51
|
|
47
52
|
if journey_routes.empty?
|
48
|
-
@
|
53
|
+
@unused_actions_errors << { controller: controller, action: action_method } if report_error
|
49
54
|
else
|
50
55
|
wrappers = journey_routes.map { |r| RouteWrapper.new(r) }
|
51
56
|
@controller_routes.concat(wrappers)
|
@@ -54,7 +59,7 @@ module RouteMechanic
|
|
54
59
|
end
|
55
60
|
end
|
56
61
|
|
57
|
-
def
|
62
|
+
def collect_unused_routes_errors(report_error)
|
58
63
|
@routes.each do |journey_route|
|
59
64
|
wrapper = RouteWrapper.new journey_route
|
60
65
|
@config_routes << wrapper
|
@@ -63,7 +68,7 @@ module RouteMechanic
|
|
63
68
|
wrapper.controller == w.controller && wrapper.action == w.action && wrapper.path == w.path
|
64
69
|
end
|
65
70
|
|
66
|
-
@
|
71
|
+
@unused_routes_errors << wrapper if !matched_controller_exist && report_error
|
67
72
|
end
|
68
73
|
end
|
69
74
|
end
|
@@ -4,7 +4,7 @@ module RouteMechanic
|
|
4
4
|
module Testing
|
5
5
|
class ErrorInspector
|
6
6
|
extend Forwardable
|
7
|
-
def_delegators :@aggregator, :
|
7
|
+
def_delegators :@aggregator, :unused_actions_errors, :unused_routes_errors
|
8
8
|
|
9
9
|
# @param [RouteMechanic::Testing::ErrorAggregator] aggregator
|
10
10
|
def initialize(aggregator)
|
@@ -15,27 +15,26 @@ module RouteMechanic
|
|
15
15
|
def message
|
16
16
|
buffer = []
|
17
17
|
|
18
|
-
if
|
18
|
+
if unused_actions_errors.present?
|
19
19
|
buffer << " No route matches to the controllers and action methods below"
|
20
|
-
buffer <<
|
20
|
+
buffer << unused_actions_errors.map {|r| " #{r[:controller]}##{r[:action]}" }
|
21
21
|
end
|
22
22
|
|
23
|
-
if
|
23
|
+
if unused_routes_errors.present?
|
24
24
|
verb_width, path_width = widths
|
25
25
|
buffer << " No controller and action matches to the routes below"
|
26
|
-
buffer <<
|
27
|
-
buffer << "\n"
|
26
|
+
buffer << unused_routes_errors.map { |w| " #{w.verb.ljust(verb_width)} #{w.path.ljust(path_width)} #{w.reqs}" }
|
28
27
|
end
|
29
28
|
|
30
|
-
["[Route Mechanic]", buffer].join("\n")
|
29
|
+
["[Route Mechanic]", buffer].join("\n") + "\n"
|
31
30
|
end
|
32
31
|
|
33
32
|
private
|
34
33
|
|
35
34
|
def widths
|
36
35
|
[
|
37
|
-
|
38
|
-
|
36
|
+
unused_routes_errors.map { |w| w.verb.length }.max || 0,
|
37
|
+
unused_routes_errors.map { |w| w.path.length }.max || 0
|
39
38
|
]
|
40
39
|
end
|
41
40
|
end
|
@@ -14,20 +14,41 @@ module RouteMechanic
|
|
14
14
|
# @param [Rails::Application] application
|
15
15
|
# @raise [Minitest::Assertion]
|
16
16
|
def assert_all_routes(application=Rails.application)
|
17
|
+
assert_targets(application, unused_actions: true, unused_routes: true)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param [Rails::Application] application
|
21
|
+
# @raise [Minitest::Assertion]
|
22
|
+
def assert_no_unused_actions(application=Rails.application)
|
23
|
+
assert_targets(application, unused_actions: true, unused_routes: false)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param [Rails::Application] application
|
27
|
+
# @raise [Minitest::Assertion]
|
28
|
+
def assert_no_unused_routes(application=Rails.application)
|
29
|
+
assert_targets(application, unused_actions: false, unused_routes: true)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# @param [Rails::Application] application
|
35
|
+
# @param [Boolean] unused_actions
|
36
|
+
# @param [Boolean] unused_routes
|
37
|
+
# @raise [Minitest::Assertion]
|
38
|
+
def assert_targets(application, unused_actions:, unused_routes:)
|
17
39
|
@application = application
|
18
40
|
|
19
41
|
# Instead of including ActionController::TestCase::Behavior, set up
|
20
42
|
# https://github.com/rails/rails/blob/5b6aa8c20a3abfd6274c83f196abf73cacb3400b/actionpack/lib/action_controller/test_case.rb#L519-L520
|
21
43
|
@controller = nil unless defined? @controller
|
22
44
|
|
23
|
-
aggregator = ErrorAggregator.new(target_routes, controllers).aggregate
|
45
|
+
aggregator = ErrorAggregator.new(target_routes, controllers).aggregate(
|
46
|
+
unused_actions: unused_actions, unused_routes: unused_routes)
|
24
47
|
aggregator.all_routes.each { |wrapper| assert_routes(wrapper) }
|
25
48
|
|
26
49
|
assert(aggregator.no_error?, ->{ aggregator.error_message })
|
27
50
|
end
|
28
51
|
|
29
|
-
private
|
30
|
-
|
31
52
|
def routes
|
32
53
|
# assert_routing expect @routes to exists as like this class inherits ActionController::TestCase.
|
33
54
|
# If user already defines @routes, do not override
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: route_mechanic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ohbarye
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-09-
|
11
|
+
date: 2020-09-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -75,6 +75,10 @@ files:
|
|
75
75
|
- fixtures/fake_app/rails_app.rb
|
76
76
|
- lib/route_mechanic.rb
|
77
77
|
- lib/route_mechanic/rspec/matchers.rb
|
78
|
+
- lib/route_mechanic/rspec/matchers/base_matcher.rb
|
79
|
+
- lib/route_mechanic/rspec/matchers/have_no_unused_actions.rb
|
80
|
+
- lib/route_mechanic/rspec/matchers/have_no_unused_routes.rb
|
81
|
+
- lib/route_mechanic/rspec/matchers/have_valid_routes.rb
|
78
82
|
- lib/route_mechanic/testing/error_aggregator.rb
|
79
83
|
- lib/route_mechanic/testing/error_inspector.rb
|
80
84
|
- lib/route_mechanic/testing/methods.rb
|