routes_coverage 0.4.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 899581bd48d52230827553a1a8f94428f7c224cc
4
- data.tar.gz: debceb6a0e49ef3d7b313c2733fc20ce3b08d428
2
+ SHA256:
3
+ metadata.gz: 576b62caebf21a9e1448f549a213eaf5770ca8486c1ad94183c1bd8617bb9916
4
+ data.tar.gz: 9a150fe8e215e92ba00dee273700ebe69f08baca5606cc7980b09fcc854c0969
5
5
  SHA512:
6
- metadata.gz: 452ab35f957e94c2122f89cb8c85599da67ea5ee8710c5090fe236d7b50a83d135221d42764fdb00d72309ebf85ea29b3e6047e30e1ef1b02dd4bc7e7ffd46f6
7
- data.tar.gz: d0288d1afe2888e8118d09f2758acf8847cab1567b88dad93d59cfd5a84423095f2a6a6af4f4dd2206dbbc33f50d1556b40667611e93c491d3f8313fe5e2c924
6
+ metadata.gz: 1695cd9694f9388c438814a990127af84930e39413694d15fec8a35bb5f12b1217c1c4a4e8d9fd3b1289a0f91543cc11d772142354481907508337c736f2217e
7
+ data.tar.gz: 4197958e82df3eceee17d222f441cbf7106d4de67d5fafb3bcdb4d16b0e59159c4678f2420c679483379aeb28a3304b246b77a8215026034aca936af7b4cb797
data/README.md CHANGED
@@ -41,6 +41,7 @@ RSpec.configure do |config|
41
41
  config.routes_coverage.exclude_namespaces << 'somenamespace' # excludes /somenamespace/*
42
42
 
43
43
  config.routes_coverage.groups["Some Route group title"] = %r{^/somespace/}
44
+ config.routes_coverage.groups["Subdomain"] = { constraints: { subdomain: 'some_subdomain' }, path: '/' }
44
45
  config.routes_coverage.groups["Admin"] = Regexp.union([
45
46
  %r{^/admin/},
46
47
  %r{^/secret_place/},
@@ -1,14 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RoutesCoverage
2
4
  module Adapters
3
5
  class AtExit
4
- def self.use coverer=nil
5
- #NB: at_exit order is important, for example minitest uses it to run, need to install our handler before it
6
+ def self.use(_coverer = nil)
7
+ # NB: at_exit order is important, for example minitest uses it to run, need to install our handler before it
6
8
 
7
9
  RoutesCoverage.reset!
8
10
  at_exit do
9
11
  next if RoutesCoverage.pid != Process.pid
12
+
10
13
  RoutesCoverage.perform_report
11
- exit
14
+ exit # rubocop:disable Rails/Exit
12
15
  end
13
16
  end
14
17
  end
@@ -1,12 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/core'
4
+
1
5
  RSpec.configure do |config|
2
6
  config.add_setting :routes_coverage
3
7
  config.routes_coverage = RoutesCoverage.settings
8
+ end
4
9
 
5
- config.before(:suite) do
6
- RoutesCoverage.reset!
7
- end
10
+ module RoutesCoverage
11
+ module Adapters
12
+ class RSpec
13
+ def self.use
14
+ ::RSpec.configure do |config|
15
+ config.before(:suite) do
16
+ RoutesCoverage.reset!
17
+ end
8
18
 
9
- config.after(:suite) do
10
- RoutesCoverage.perform_report
19
+ config.after(:suite) do
20
+ RoutesCoverage.perform_report
21
+ end
22
+ end
23
+ end
24
+ end
11
25
  end
12
26
  end
@@ -1,11 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RoutesCoverage
2
4
  module Adapters
3
5
  class SimpleCov
4
6
  def self.use
5
7
  RoutesCoverage.reset!
6
- ::SimpleCov.at_exit{
8
+ prev_block = ::SimpleCov.at_exit
9
+ ::SimpleCov.at_exit do
7
10
  RoutesCoverage.perform_report
8
- }
11
+ prev_block.call
12
+ end
9
13
  end
10
14
  end
11
15
  end
@@ -1,15 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RoutesCoverage
2
4
  module Formatters
3
5
  class Base
4
- def initialize result, groups, settings
6
+ def initialize(result, groups, settings)
5
7
  @result = result
6
8
  @groups = groups
7
9
  @settings = settings
8
10
  end
9
11
 
10
- attr_reader :result
11
- attr_reader :groups
12
- attr_reader :settings
12
+ attr_reader :result, :groups, :settings
13
13
  end
14
14
  end
15
15
  end
@@ -1,11 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RoutesCoverage
2
4
  module Formatters
3
5
  class FullText < SummaryText
4
-
5
6
  class RouteFormatter
6
7
  attr_reader :buffer
7
8
 
8
- def initialize result=nil, _settings=nil, output_hits=false
9
+ def initialize(result = nil, _settings = nil, output_hits: false)
9
10
  @buffer = []
10
11
  @result = result
11
12
  @output_hits = output_hits
@@ -28,49 +29,47 @@ module RoutesCoverage
28
29
  @buffer << draw_header(routes)
29
30
  end
30
31
 
31
- def no_routes _routes_from_rails5=nil
32
+ def no_routes(_routes_from_rails5 = nil)
32
33
  @buffer << "\tNone"
33
34
  end
34
35
 
35
36
  private
36
- HEADER = ['Prefix', 'Verb', 'URI Pattern', 'Controller#Action']
37
+
38
+ HEADER = ['Prefix', 'Verb', 'URI Pattern', 'Controller#Action'].freeze
37
39
  def draw_section(routes)
38
40
  header_lengths = HEADER.map(&:length)
39
41
  name_width, verb_width, path_width, reqs_width = widths(routes).zip(header_lengths).map(&:max)
40
42
 
41
- hits = nil
42
43
  routes.map do |r|
43
- # puts "route is #{r.inspect}"
44
- if @output_hits
45
- # hits = " ?"
46
- if r[:original].respond_to?(:__getobj__)
47
- original_route = r[:original].__getobj__ # SimpleDelegator
48
- else
49
- original_route = r[:original]
50
- end
51
- hits = " #{@result.route_hit_counts[original_route]}"
52
- end
53
- "#{r[:name].rjust(name_width) if @output_prefix} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs].ljust(reqs_width)}#{hits}"
44
+ # unwrap SimpleDelegator
45
+ original_route = r[:original].respond_to?(:__getobj__) ? r[:original].__getobj__ : r[:original]
46
+
47
+ "#{r[:name].rjust(name_width) if @output_prefix} "\
48
+ "#{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs].ljust(reqs_width)}"\
49
+ "#{" #{@result.route_hit_counts[original_route]}" if @output_hits}"
54
50
  end
55
51
  end
56
52
 
57
53
  def draw_header(routes)
58
54
  name_width, verb_width, path_width, reqs_width = widths(routes)
59
55
 
60
- "#{"Prefix".rjust(name_width) if @output_prefix} #{"Verb".ljust(verb_width)} #{"URI Pattern".ljust(path_width)} #{"Controller#Action".ljust(reqs_width)}#{' Hits' if @output_hits}"
56
+ [
57
+ ('Prefix'.rjust(name_width) if @output_prefix),
58
+ 'Verb'.ljust(verb_width),
59
+ 'URI Pattern'.ljust(path_width),
60
+ 'Controller#Action'.ljust(reqs_width),
61
+ ('Hits' if @output_hits)
62
+ ].compact.join(' ')
61
63
  end
62
64
 
63
65
  def widths(routes)
64
- [routes.map { |r| r[:name].length }.max || 0,
65
- routes.map { |r| r[:verb].length }.max || 0,
66
- routes.map { |r| r[:path].length }.max || 0,
67
- routes.map { |r| r[:reqs].length }.max || 0,
68
- ]
66
+ %i[name verb path reqs].map do |key|
67
+ routes.map { |r| r[key].length }.max.to_i
68
+ end
69
69
  end
70
70
  end
71
71
 
72
-
73
- def routes_section formatter, title, routes
72
+ def routes_section(formatter, title, routes)
74
73
  formatter.buffer << title
75
74
 
76
75
  if routes.none?
@@ -83,24 +82,27 @@ module RoutesCoverage
83
82
  formatter.result
84
83
  end
85
84
 
86
- def hit_routes
87
- routes = result.hit_routes
85
+ def collect_routes(routes)
86
+ # rails 3
87
+ return Result::Inspector.new.collect_all_routes(routes) unless Result::Inspector::NEW_RAILS
88
+
89
+ Result::Inspector.new(routes).collect_all_routes
90
+ end
91
+
92
+ def hit_routes_details
88
93
  # engine routes now are in the same list
89
- if Result::Inspector::NEW_RAILS
90
- hit_routes = Result::Inspector.new(result.hit_routes).collect_all_routes
91
- pending_routes = Result::Inspector.new(result.pending_routes).collect_all_routes
92
- else
93
- #rails 3
94
- hit_routes = Result::Inspector.new.collect_all_routes(result.hit_routes)
95
- pending_routes = Result::Inspector.new.collect_all_routes(result.pending_routes)
96
- end
94
+ hit_routes = collect_routes(result.hit_routes)
95
+ pending_routes = collect_routes(result.pending_routes)
96
+
97
+ <<~TXT
98
+ #{routes_section(RouteFormatter.new(result, settings, output_hits: true), 'Covered routes:', hit_routes)}
97
99
 
98
- return routes_section(RouteFormatter.new(result, settings, true), "Covered routes:", hit_routes) + "\n\n" +
99
- routes_section(RouteFormatter.new(result, settings), "Pending routes:", pending_routes)
100
+ #{routes_section(RouteFormatter.new(result, settings), 'Pending routes:', pending_routes)}
101
+ TXT
100
102
  end
101
103
 
102
104
  def format
103
- "#{super}\n\n#{hit_routes}"
105
+ "#{super}\n\n#{hit_routes_details}"
104
106
  end
105
107
  end
106
108
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "erb"
2
4
  require "cgi"
3
5
  require "fileutils"
@@ -23,24 +25,25 @@ module RoutesCoverage
23
25
  ERB.new(File.read(File.join(File.dirname(__FILE__), "html_views", "#{name}.erb")))
24
26
  end
25
27
 
26
-
27
28
  def root(root = nil)
28
- #TODO: config for this
29
+ # TODO: config for this
29
30
  return SimpleCov.root if defined? SimpleCov
30
31
  return @root if defined?(@root) && root.nil?
32
+
31
33
  @root = File.expand_path(root || Dir.getwd)
32
34
  end
33
35
 
34
36
  def coverage_dir(dir = nil)
35
- #TODO: config for this
37
+ # TODO: config for this
36
38
  return SimpleCov.coverage_dir if defined? SimpleCov
37
39
  return @coverage_dir if defined?(@coverage_dir) && dir.nil?
38
- @coverage_path = nil # invalidate cache
40
+
41
+ @output_path = nil # invalidate cache
39
42
  @coverage_dir = (dir || "coverage")
40
43
  end
41
44
 
42
45
  def output_path
43
- @coverage_path ||= File.expand_path(coverage_dir, root).tap do |path|
46
+ @output_path ||= File.expand_path(coverage_dir, root).tap do |path|
44
47
  FileUtils.mkdir_p path
45
48
  end
46
49
  end
@@ -51,8 +54,8 @@ module RoutesCoverage
51
54
  @project_name ||= File.basename(root.split("/").last).capitalize.tr("_", " ")
52
55
  end
53
56
 
54
- def asset_content name
55
- File.read(File.expand_path("../../../compiled_assets/#{name}", File.dirname(__FILE__)))
57
+ def asset_content(name)
58
+ File.read(File.expand_path("../../../compiled_assets/#{name}", __dir__))
56
59
  end
57
60
 
58
61
  def style_asset_link
@@ -83,33 +86,23 @@ module RoutesCoverage
83
86
  end
84
87
  end
85
88
 
86
- def hits_css_class hits
87
- hits > 0 ? 'cov' : 'uncov'
89
+ def hits_css_class(hits)
90
+ hits.positive? ? 'cov' : 'uncov'
88
91
  end
89
92
 
90
93
  def timeago(time)
91
94
  "<abbr class=\"timeago\" title=\"#{time.iso8601}\">#{time.iso8601}</abbr>"
92
95
  end
93
96
 
94
-
95
97
  def all_result_groups
96
- return @all_result_groups if @all_result_groups
97
- @all_result_groups = [
98
- {
99
- id: 'all_routes',
100
- name: 'All Routes',
101
- result: result,
102
- }
103
- ]
104
- @all_result_groups += groups.map do |group_name, group_result|
105
- {
106
- id: group_name.gsub(/^[^a-zA-Z]+/, "").gsub(/[^a-zA-Z0-9\-\_]/, ""),
107
- name: group_name,
108
- result: group_result,
109
- }
110
- end
111
-
112
- @all_result_groups
98
+ @all_result_groups ||= [{ id: 'all_routes', name: 'All Routes', result: result }] +
99
+ groups.map do |group_name, group_result|
100
+ {
101
+ id: group_name.gsub(/^[^a-zA-Z]+/, "").gsub(/[^a-zA-Z0-9\-_]/, ""),
102
+ name: group_name,
103
+ result: group_result
104
+ }
105
+ end
113
106
  end
114
107
  end
115
108
  end
@@ -1,15 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RoutesCoverage
2
4
  module Formatters
3
5
  class SummaryText < Base
4
- def hits_count result
5
- "#{result.hit_routes_count} of #{result.expected_routes_count}#{"(#{result.total_count} total)" if result.expected_routes_count != result.total_count} routes hit#{ " at #{result.avg_hits} hits average" if result.hit_routes_count > 0}"
6
+ def hits_count(result)
7
+ "#{result.hit_routes_count} of #{result.expected_routes_count}"\
8
+ "#{"(#{result.total_count} total)" if result.expected_routes_count != result.total_count}"\
9
+ " routes hit#{" at #{result.avg_hits} hits average" if result.hit_routes_count.positive?}"
6
10
  end
7
11
 
8
12
  def status
9
13
  return unless settings.minimum_coverage
10
- unless result.coverage_pass?
11
- "Coverage is too low"
12
- end
14
+
15
+ "Coverage is too low" unless result.coverage_pass?
13
16
  end
14
17
 
15
18
  def format
@@ -18,9 +21,9 @@ module RoutesCoverage
18
21
  ]
19
22
 
20
23
  if groups.any?
21
- buffer += groups.map{|group_name, group_result|
24
+ buffer += groups.map do |group_name, group_result|
22
25
  " #{group_name}: #{group_result.coverage}% (#{hits_count group_result})"
23
- }
26
+ end
24
27
  end
25
28
 
26
29
  buffer << status
@@ -1,26 +1,27 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RoutesCoverage
2
4
  class Middleware
3
- def initialize app
5
+ def initialize(app)
4
6
  @app = app
5
7
  end
6
8
 
7
- def call env
9
+ def call(original_env)
8
10
  # router changes env/request during recognition so need a copy:
9
- req = ::Rails.application.routes.request_class.new env.dup
10
- ::Rails.application.routes.router.recognize(req) do |route|
11
+ env = original_env.dup
12
+ req = ::Rails.application.routes.request_class.new env
13
+ ::Rails.application.routes.router.recognize(req) do |route, parameters5, parameters4|
14
+ parameters = parameters5 || parameters4
11
15
  dispatcher = route.app
12
16
  if dispatcher.respond_to?(:dispatcher?)
13
- unless dispatcher.matches?(req) # && dispatcher.dispatcher?
14
- dispatcher = nil
15
- end
17
+ req.path_parameters = parameters
18
+ dispatcher = nil unless dispatcher.matches?(req) # && dispatcher.dispatcher?
16
19
  else # rails < 4.2
17
20
  dispatcher = route.app
18
- while dispatcher.is_a?(ActionDispatch::Routing::Mapper::Constraints) do
19
- if dispatcher.matches?(env)
20
- dispatcher = dispatcher.app
21
- else
22
- dispatcher = nil
23
- end
21
+ req.env['action_dispatch.request.path_parameters'] =
22
+ (env['action_dispatch.request.path_parameters'] || {}).merge(parameters)
23
+ while dispatcher.is_a?(ActionDispatch::Routing::Mapper::Constraints)
24
+ dispatcher = (dispatcher.app if dispatcher.matches?(env))
24
25
  end
25
26
  end
26
27
  next unless dispatcher
@@ -29,8 +30,8 @@ module RoutesCoverage
29
30
  # there may be multiple matching routes - we should match only first
30
31
  break
31
32
  end
32
- #TODO: detect 404s? and maybe other route errors?
33
- @app.call(env)
33
+ # TODO: detect 404s? and maybe other route errors?
34
+ @app.call(original_env)
34
35
  end
35
36
  end
36
37
  end
@@ -1,19 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/core_ext/string' # needed for rails5 version of inspector
2
4
 
3
5
  module RoutesCoverage
4
6
  class Result
5
-
6
7
  begin
7
8
  require 'action_dispatch/routing/inspector'
8
9
  class Inspector < ActionDispatch::Routing::RoutesInspector
9
10
  NEW_RAILS = true
10
11
  def collect_all_routes
11
12
  res = collect_routes(@routes)
12
- #TODO: test with engines
13
+ # TODO: test with engines
13
14
  @engines.each do |engine_name, engine_routes|
14
- res += engine_routes.map{|er|
15
+ res += engine_routes.map do |er|
15
16
  er.merge({ engine_name: engine_name })
16
- }
17
+ end
17
18
  end
18
19
  res
19
20
  end
@@ -21,39 +22,35 @@ module RoutesCoverage
21
22
  def collect_routes(routes)
22
23
  routes.collect do |route|
23
24
  ActionDispatch::Routing::RouteWrapper.new(route)
24
- end.reject do |route|
25
- route.internal?
26
- end.collect do |route|
25
+ end.reject(&:internal?).collect do |route| # rubocop:disable Style/MultilineBlockChain
27
26
  collect_engine_routes(route)
28
27
 
29
- { name: route.name,
30
- verb: route.verb,
31
- path: route.path,
32
- reqs: route.reqs,
28
+ { name: route.name,
29
+ verb: route.verb,
30
+ path: route.path,
31
+ reqs: route.reqs,
33
32
  # regexp: route.json_regexp, # removed, this is not present in rails5
34
33
  # added:
35
- original: route,
36
- }
34
+ original: route }
37
35
  end
38
36
  end
39
37
  end
40
-
41
38
  rescue LoadError
42
- #rails 3
39
+ # rails 3
43
40
  require 'rails/application/route_inspector'
44
41
  class Inspector < Rails::Application::RouteInspector
45
42
  NEW_RAILS = false
46
- def collect_all_routes(routes)
43
+ def collect_all_routes(routes) # rubocop:disable Lint/DuplicateMethods
47
44
  res = collect_routes(routes)
48
45
  @engines.each do |engine_name, engine_routes|
49
- res += engine_routes.map{|er|
46
+ res += engine_routes.map do |er|
50
47
  er.merge({ engine_name: engine_name })
51
- }
48
+ end
52
49
  end
53
50
  res
54
51
  end
55
52
 
56
- def collect_routes(routes)
53
+ def collect_routes(routes) # rubocop:disable Lint/DuplicateMethods
57
54
  routes = routes.collect do |route|
58
55
  route_reqs = route.requirements
59
56
 
@@ -70,44 +67,40 @@ module RoutesCoverage
70
67
 
71
68
  collect_engine_routes(reqs, rack_app)
72
69
 
73
- { name: route.name.to_s,
74
- verb: route.verb.source.gsub(/[$^]/, ''),
75
- path: route.path.spec.to_s,
76
- reqs: reqs,
70
+ { name: route.name.to_s,
71
+ verb: route.verb.source.gsub(/[$^]/, ''),
72
+ path: route.path.spec.to_s,
73
+ reqs: reqs,
77
74
  # added:
78
- original: route,
79
- }
75
+ original: route }
80
76
  end
81
77
 
82
78
  # Skip the route if it's internal info route
83
79
  routes.reject { |r| r[:path] =~ %r{/rails/info/properties|^#{Rails.application.config.assets.prefix}} }
84
80
  end
85
81
  end
86
-
87
82
  end
88
83
 
89
- def initialize all_routes, hit_routes, settings
84
+ def initialize(all_routes, hit_routes, settings)
90
85
  @all_routes = all_routes
91
86
  @route_hit_counts = hit_routes
92
87
  @settings = settings
93
88
  end
94
89
 
95
- attr_reader :all_routes
96
-
97
- attr_reader :route_hit_counts
90
+ attr_reader :all_routes, :route_hit_counts
98
91
 
99
92
  def expected_routes
100
93
  return @expected_routes if @expected_routes
101
94
 
102
95
  filter_regex = Regexp.union(@settings.exclude_patterns)
103
- namespaces_regex = Regexp.union(@settings.exclude_namespaces.map{|n| /^\/#{n}/})
96
+ namespaces_regex = Regexp.union(@settings.exclude_namespaces.map { |n| %r{^/#{n}} })
104
97
 
105
- routes_groups = all_routes.group_by{|r|
106
- !!(
98
+ routes_groups = all_routes.group_by do |r|
99
+ (
107
100
  ("#{r.verb.to_s[8..-3]} #{r.path.spec}".strip =~ filter_regex) ||
108
101
  (r.path.spec.to_s =~ namespaces_regex)
109
- )
110
- }
102
+ ).present?
103
+ end
111
104
 
112
105
  @excluded_routes = routes_groups[true] || []
113
106
  @expected_routes = routes_groups[false] || []
@@ -123,11 +116,10 @@ module RoutesCoverage
123
116
  end
124
117
 
125
118
  def hit_routes
126
- #TODO: sort?
119
+ # TODO: sort?
127
120
  @route_hit_counts.keys
128
121
  end
129
122
 
130
-
131
123
  def hit_routes_count
132
124
  @route_hit_counts.size
133
125
  end
@@ -146,6 +138,7 @@ module RoutesCoverage
146
138
 
147
139
  def coverage
148
140
  return 0 unless expected_routes.any?
141
+
149
142
  (hit_routes_count * 100.0 / expected_routes_count).round(@settings.round_precision)
150
143
  end
151
144
 
@@ -158,16 +151,15 @@ module RoutesCoverage
158
151
  end
159
152
 
160
153
  def all_routes_with_hits
161
- if Inspector::NEW_RAILS
162
- res = Inspector.new(all_routes).collect_all_routes
163
- else
164
- res = Inspector.new.collect_all_routes(all_routes)
165
- end
166
- res.each{|val|
154
+ res = if Inspector::NEW_RAILS
155
+ Inspector.new(all_routes).collect_all_routes
156
+ else
157
+ Inspector.new.collect_all_routes(all_routes)
158
+ end
159
+ res.each do |val|
167
160
  val[:hits] = @route_hit_counts[val[:original]] || 0
168
- }
161
+ end
169
162
  res
170
163
  end
171
-
172
164
  end
173
165
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RoutesCoverage
2
- VERSION = "0.4.0"
4
+ VERSION = "0.5.0"
3
5
  end