swagcov 0.2.4 → 0.4.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
2
  SHA256:
3
- metadata.gz: 83f05163f3408b08db9f9810e5eb82a4285877d9e7b7af60a83e44386eab5c04
4
- data.tar.gz: 76b1bf03e51bc42e516f5b77536d1ae38685dd642823094a0ac8e67fa8c67ac3
3
+ metadata.gz: 8aec9444a5e8dfd1d4b23fc61c710d4ccc28d5c7985df0fbef5e8c02e16e7519
4
+ data.tar.gz: a19a3c8f73ad9efb814ff7469b6306bacb52d296893130b7fb42740d1d7e7db4
5
5
  SHA512:
6
- metadata.gz: 82610986597a8a832173354fb452a4d63c7c0a22a342ec10fd2ac6bcb3eef845d306c6d37225ac837b22febcb12a0ba093167b71d3acc94aaabb8394789b77a7
7
- data.tar.gz: a1cdf7691af9fce07dfc50711a748bcbfe903ef0f5f5b94e3d1b3d7fd605ac125de92db23121a8ccdb8622494bfba431f7f3710e55f6392a20160e5c2bfd4e4f
6
+ metadata.gz: cc64199a92c0322fe3c42bbaa882d103a9a8f18e1c3dbe02ddb5b56bb21d13eae763536fbcdb2f84aa8a045fb6d92726f1a953eaa27595118cbe6a7324dd5c2a
7
+ data.tar.gz: 9713fa38210e7f6273e2f9004a7dc791994ebd80e50e0852381de005f67b1c238ce995060c3643b2b1db7520d46687defa554067e3ff2a75ae71ec70a809f496
data/README.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Swagcov
2
- See coverage report for openapi docs for Rails Routes.
2
+ [![Gem Version](https://img.shields.io/gem/v/swagcov)](https://rubygems.org/gems/swagcov)
3
+ ![Gem Downloads](https://img.shields.io/gem/dt/swagcov)
4
+ [![Ruby Style Guide](https://img.shields.io/badge/code_style-rubocop-brightgreen.svg)](https://github.com/rubocop-hq/rubocop)
5
+ [![GitHub License](https://img.shields.io/github/license/smridge/swagcov.svg)](https://github.com/smridge/swagcov/blob/main/LICENSE)
6
+
7
+ See OpenAPI documentation coverage report for Rails Routes.
8
+
9
+ ## Usages
10
+ - See overview of different endpoints covered, missing and what you choose to ignore.
11
+ - Add pass/fail to your build pipeline when missing Documentation Coverage.
3
12
 
4
13
  ## Installation
5
14
  Add this line to your application's Gemfile:
@@ -17,7 +26,7 @@ Create a `.swagcov.yml` in root of your Rails application.
17
26
  ```yml
18
27
  docs:
19
28
  paths:
20
- - swagger/v1/swagger.yaml
29
+ - swagger.yaml
21
30
  ```
22
31
 
23
32
  - Add `only` routes (**optional**) :
@@ -34,14 +43,13 @@ Create a `.swagcov.yml` in root of your Rails application.
34
43
  paths:
35
44
  ignore:
36
45
  - /v1/foobar/:token
37
- - /sidekiq
38
46
  ```
39
47
 
40
- - Example `.swagcov.yml` Config File:
48
+ - Full Example `.swagcov.yml` Config File:
41
49
  ```yml
42
50
  docs:
43
51
  paths:
44
- - swagger/v1/swagger.yaml
52
+ - swagger.yaml
45
53
 
46
54
  routes:
47
55
  paths:
@@ -49,7 +57,6 @@ Create a `.swagcov.yml` in root of your Rails application.
49
57
  - ^/v1
50
58
  ignore:
51
59
  - /v1/foobar/:token
52
- - /sidekiq
53
60
  ```
54
61
 
55
62
  Execute:
@@ -57,6 +64,44 @@ Execute:
57
64
  bundle exec rake swagcov
58
65
  ```
59
66
 
67
+ ### Example configurations and output from running `bundle exec rake swagcov` from the root of your Rails Application:
68
+ - All Routes (minimal configuration):
69
+ ```yml
70
+ docs:
71
+ paths:
72
+ - swagger.yaml
73
+ ```
74
+ <img src="https://raw.githubusercontent.com/smridge/swagcov/main/images/all-endpoints.png">
75
+
76
+
77
+ - With `only` endpoint configuration:
78
+ ```yml
79
+ docs:
80
+ paths:
81
+ - swagger.yaml
82
+
83
+ routes:
84
+ paths:
85
+ only:
86
+ - ^/v2
87
+ ```
88
+ <img src="https://raw.githubusercontent.com/smridge/swagcov/main/images/only-endpoints.png">
89
+
90
+ - With `ignore` and `only` endpoint configurations:
91
+ ```yml
92
+ docs:
93
+ paths:
94
+ - swagger.yaml
95
+
96
+ routes:
97
+ paths:
98
+ only:
99
+ - ^/v2
100
+ ignore:
101
+ - /v2/users
102
+ ```
103
+ <img src="https://raw.githubusercontent.com/smridge/swagcov/main/images/ignore-and-only-endpoints.png">
104
+
60
105
  ## Development
61
106
  ```shell
62
107
  git clone git@github.com:smridge/swagcov.git
@@ -72,22 +117,41 @@ gem "swagcov", path: "../swagcov"
72
117
  bundle
73
118
  ```
74
119
 
120
+ Run Tests
121
+ ```
122
+ bundle exec rspec spec --exclude-pattern spec/sandbox_**/**/*_spec.rb
123
+ ```
124
+
125
+ ### Test via Sandbox Application
126
+ - Go to any sandbox application within the `spec` directory
127
+ - For example, `cd spec/sandbox_5_2/`
128
+ - Install the ruby version specified in `.ruby-version` file
129
+ - Install gems: `bundle install`
130
+ - Run tests: `bundle exec rspec spec`
131
+ - Run `bundle exec rake swagcov`
132
+ - This will run against any changes made to your branch.
133
+
75
134
  ## Publish (internal)
76
135
  > Note: Publishing a new version of this gem is only meant for maintainers.
77
136
  - Ensure you have access to publish on [rubygems](https://rubygems.org/gems/swagcov).
78
137
  - Update [CHANGELOG](https://github.com/smridge/swagcov/blob/main/CHANGELOG.md).
79
138
  - Update [`VERSION`](https://github.com/smridge/swagcov/blob/main/lib/swagcov/version.rb).
80
- - Commit changes to `main` branch locally.
139
+ - Run `bundle update` for each sandbox application to reflect new swagcov version in each `Gemfile.lock`
140
+ - Open a Pull Request to ensure all specs pass, then merge to `main`.
141
+ - Checkout the latest `main` on your machine.
81
142
  - Run: `rake release`
82
143
  - This command builds the gem, creates a tag and publishes to rubygems, see [bundler docs](https://bundler.io/guides/creating_gem.html#releasing-the-gem).
83
144
 
84
145
  ## TODO
85
- - Add specs
86
- - Test against different rails versions
87
- - Create Sandbox Apps for Rails 5 & 6
146
+ - Create Rails 7 / Ruby 3.1 Sandbox
88
147
  - Add autogeneration of ignore paths
89
148
  - Add `CONTRIBUTING.md`
90
- - Add GitHub Actions for specs/linting
149
+ - Add GitHub Action for linting
91
150
 
92
151
  ## Credit
93
152
  To [@lonelyelk](https://github.com/lonelyelk) for initial development!
153
+
154
+ ## Contributors
155
+ <a href="https://github.com/smridge/swagcov/graphs/contributors">
156
+ <img src="https://contrib.rocks/image?repo=smridge/swagcov" />
157
+ </a>
@@ -2,31 +2,41 @@
2
2
 
3
3
  module Swagcov
4
4
  class Coverage
5
- def initialize
5
+ attr_reader :total, :covered, :ignored, :routes_not_covered, :routes_covered, :routes_ignored
6
+
7
+ def initialize dotfile: Swagcov::Dotfile.new, routes: ::Rails.application.routes.routes
6
8
  @total = 0
7
9
  @covered = 0
8
10
  @ignored = 0
9
11
  @routes_not_covered = []
10
12
  @routes_covered = []
11
13
  @routes_ignored = []
12
- @dotfile = Swagcov::Dotfile.new
14
+ @dotfile = dotfile
15
+ @routes = routes
13
16
  end
14
17
 
15
18
  def report
16
- Rails.application.routes.routes.each do |route|
17
- # https://github.com/rails/rails/blob/48f3c3e201b57a4832314b2c957a3b303e89bfea/actionpack/lib/action_dispatch/routing/inspector.rb#L105-L107
18
- # Skips route paths like ["/rails/info/properties", "/rails/info", "/rails/mailers"]
19
- next if route.internal
19
+ collect_coverage
20
+ routes_output(@routes_covered, "green")
21
+ routes_output(@routes_ignored, "yellow")
22
+ routes_output(@routes_not_covered, "red")
20
23
 
21
- # Skips routes like "/sidekiq"
22
- next unless route.verb.present?
24
+ final_output
23
25
 
24
- path = route.path.spec.to_s.sub(/\(\.:format\)$/, "")
26
+ @total - @covered
27
+ end
25
28
 
26
- # Exclude routes that are part of the rails gem that you would not write documentation for
27
- # https://github.com/rails/rails/tree/main/activestorage/app/controllers/active_storage
28
- # https://github.com/rails/rails/tree/main/actionmailbox/app/controllers/action_mailbox
29
- next if path.include?("/active_storage/") || path.include?("/action_mailbox/")
29
+ private
30
+
31
+ attr_reader :dotfile
32
+
33
+ def collect_coverage
34
+ openapi_files = ::Swagcov::OpenapiFiles.new(filepaths: dotfile.doc_paths)
35
+
36
+ @routes.each do |route|
37
+ path = route.path.spec.to_s.chomp("(.:format)")
38
+
39
+ next if third_party_route?(route, path)
30
40
 
31
41
  if dotfile.ignore_path?(path)
32
42
  @ignored += 1
@@ -37,41 +47,35 @@ module Swagcov
37
47
  next if dotfile.only_path_mismatch?(path)
38
48
 
39
49
  @total += 1
40
- regex = Regexp.new("#{path.gsub(%r{:[^/]+}, '\\{[^/]+\\}')}(\\.[^/]+)?$")
41
- matching_keys = docs_paths.keys.select { |k| regex.match?(k) }
42
50
 
43
- if (doc = docs_paths.dig(matching_keys.first, route.verb.downcase))
51
+ if (response_keys = openapi_files.find_response_keys(path: path, route_verb: route.verb))
44
52
  @covered += 1
45
- @routes_covered << { verb: route.verb, path: path, status: doc["responses"].keys.map(&:to_s).sort.join(" ") }
53
+ @routes_covered << { verb: route.verb, path: path, status: response_keys.join(" ") }
46
54
  else
47
55
  @routes_not_covered << { verb: route.verb, path: path, status: "none" }
48
56
  end
49
57
  end
50
-
51
- routes_output(@routes_covered, "green")
52
- routes_output(@routes_ignored, "yellow")
53
- routes_output(@routes_not_covered, "red")
54
-
55
- final_output
56
-
57
- exit @total - @covered
58
58
  end
59
59
 
60
- def docs_paths
61
- @docs_paths ||= Dir.glob(dotfile.doc_paths).reduce({}) do |acc, docspath|
62
- acc.merge(YAML.load_file(docspath)["paths"])
63
- end
64
- end
60
+ def third_party_route? route, path
61
+ # https://github.com/rails/rails/blob/48f3c3e201b57a4832314b2c957a3b303e89bfea/actionpack/lib/action_dispatch/routing/inspector.rb#L105-L107
62
+ # Skips route paths like ["/rails/info/properties", "/rails/info", "/rails/mailers"]
63
+ route.internal ||
65
64
 
66
- private
65
+ # Skips routes like "/sidekiq"
66
+ route.verb.blank? ||
67
67
 
68
- attr_reader :dotfile
68
+ # Exclude routes that are part of the rails gem that you would not write documentation for
69
+ # https://github.com/rails/rails/tree/main/activestorage/app/controllers/active_storage
70
+ # https://github.com/rails/rails/tree/main/actionmailbox/app/controllers/action_mailbox
71
+ path.include?("/active_storage/") || path.include?("/action_mailbox/")
72
+ end
69
73
 
70
74
  def routes_output routes, status_color
71
75
  routes.each do |route|
72
76
  $stdout.puts(
73
77
  format(
74
- "%<verb>10s %<path>-90s %<status>s",
78
+ "%<verb>10s %<path>-90s %<status>s",
75
79
  { verb: route[:verb], path: route[:path], status: route[:status].send(status_color) }
76
80
  )
77
81
  )
@@ -5,12 +5,12 @@ module Swagcov
5
5
  end
6
6
 
7
7
  class Dotfile
8
- def initialize
9
- pathname = Rails.root.join(".swagcov.yml")
10
- raise BadConfigurationError, "Missing .swagcov.yml" unless pathname.exist?
8
+ DEFAULT_CONFIG_FILE_NAME = ".swagcov.yml"
11
9
 
12
- @dotfile = YAML.load_file(pathname)
13
- raise BadConfigurationError, "Invalid .swagcov.yml" unless valid?
10
+ def initialize pathname: ::Rails.root.join(DEFAULT_CONFIG_FILE_NAME)
11
+ @dotfile = load_yaml(pathname)
12
+
13
+ raise BadConfigurationError, "Invalid config file (#{DEFAULT_CONFIG_FILE_NAME})" unless valid?
14
14
  end
15
15
 
16
16
  def ignore_path? path
@@ -29,6 +29,14 @@ module Swagcov
29
29
 
30
30
  attr_reader :dotfile
31
31
 
32
+ def load_yaml pathname
33
+ raise BadConfigurationError, "Missing config file (#{DEFAULT_CONFIG_FILE_NAME})" unless pathname.exist?
34
+
35
+ YAML.load_file(pathname)
36
+ rescue Psych::SyntaxError
37
+ raise BadConfigurationError, "Malinformed config file (#{DEFAULT_CONFIG_FILE_NAME})"
38
+ end
39
+
32
40
  def ignored_regex
33
41
  @ignored_regex ||= path_config_regex(dotfile.dig("routes", "paths", "ignore"))
34
42
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Swagcov
4
+ class OpenapiFiles
5
+ def initialize filepaths:
6
+ @filepaths = filepaths
7
+ @openapi_paths = load_yamls
8
+ end
9
+
10
+ def find_response_keys path:, route_verb:
11
+ # replace :id with {id}
12
+ regex = Regexp.new("^#{path.gsub(%r{:[^/]+}, '\\{[^/]+\\}')}?$")
13
+
14
+ matching_paths_key = @openapi_paths.keys.grep(regex).first
15
+ matching_request_method_key = @openapi_paths.dig(matching_paths_key, route_verb.downcase)
16
+
17
+ matching_request_method_key["responses"].keys.map(&:to_s).sort if matching_request_method_key
18
+ end
19
+
20
+ private
21
+
22
+ def load_yamls
23
+ Dir.glob(@filepaths).reduce({}) do |hash, filepath|
24
+ hash.merge(load_yaml(filepath))
25
+ end
26
+ end
27
+
28
+ def load_yaml filepath
29
+ YAML.load_file(filepath)["paths"]
30
+ rescue Psych::SyntaxError
31
+ raise BadConfigurationError, "Malinformed openapi file (#{filepath})"
32
+ end
33
+ end
34
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Swagcov
4
- VERSION = "0.2.4"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/swagcov.rb CHANGED
@@ -6,6 +6,7 @@ if defined?(Rails)
6
6
  require "swagcov/railtie"
7
7
  require "swagcov/dotfile"
8
8
  require "swagcov/coverage"
9
+ require "swagcov/openapi_files"
9
10
  end
10
11
 
11
12
  require "swagcov/core_ext/string"
@@ -2,5 +2,5 @@
2
2
 
3
3
  desc "check documentation coverage for endpoints"
4
4
  task swagcov: :environment do
5
- Swagcov::Coverage.new.report
5
+ exit Swagcov::Coverage.new.report
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: swagcov
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sarah Ridge
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-30 00:00:00.000000000 Z
11
+ date: 2022-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry-byebug
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rspec
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +80,20 @@ dependencies:
66
80
  - - ">="
67
81
  - !ruby/object:Gem::Version
68
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
69
97
  description:
70
98
  email:
71
99
  - sarahmarie@hey.com
@@ -80,6 +108,7 @@ files:
80
108
  - lib/swagcov/core_ext/string.rb
81
109
  - lib/swagcov/coverage.rb
82
110
  - lib/swagcov/dotfile.rb
111
+ - lib/swagcov/openapi_files.rb
83
112
  - lib/swagcov/railtie.rb
84
113
  - lib/swagcov/version.rb
85
114
  - lib/tasks/swagcove.rake
@@ -91,6 +120,7 @@ metadata:
91
120
  homepage_uri: https://github.com/smridge/swagcov
92
121
  source_code_uri: https://github.com/smridge/swagcov
93
122
  changelog_uri: https://github.com/smridge/swagcov/blob/main/CHANGELOG.md
123
+ rubygems_mfa_required: 'true'
94
124
  post_install_message:
95
125
  rdoc_options: []
96
126
  require_paths:
@@ -106,7 +136,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
106
136
  - !ruby/object:Gem::Version
107
137
  version: '0'
108
138
  requirements: []
109
- rubygems_version: 3.1.4
139
+ rubygems_version: 3.2.33
110
140
  signing_key:
111
141
  specification_version: 4
112
142
  summary: Open API docs coverage for Rails Routes