reqres_rspec 0.0.1 → 0.0.2

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
  SHA1:
3
- metadata.gz: fff2afe79f68435c6b2427a0956a13b7fcc745df
4
- data.tar.gz: 8e5a3cc6f86c39a9298fd32eb95d3ab3173736db
3
+ metadata.gz: 6ab09afc849b19636a0c584016db5aa14da06d70
4
+ data.tar.gz: cf117741746332f50063c8bcd3a8224901e1fc6a
5
5
  SHA512:
6
- metadata.gz: 2e2d2c9c66a38dc962c9e99e7bcbfda25851ce1de6fc9481289708e14fb11edf5180c49ee1458b8ddd541230fd13d71853ee29ee6b6a8e0acb33eb68b9c1c7f2
7
- data.tar.gz: 38831de650a0ace22d13ccbcfa96b19ba9694d1c80f12954c6974be12d6e4dfd224538a042dd52ffd487ce87d227afe51385189c594c2eaa0e3fd7f3d4e36286
6
+ metadata.gz: a295bee73cb02b837727c841621946984d233c241a2f85a4700d7d6d4772aeb1809d4c84aa58533785df741b49b6272085a36a0c698f70b894800e77fd95ea72
7
+ data.tar.gz: d35cef61612ac710074bba1700ef3023636225fb9ee0328c2eecb348936f3dce3a342c90cfee668b8ea73364ffcd800a6273e31296c8fa1682f2ff018df14781
@@ -1,14 +1,198 @@
1
- require 'reqres_rspec'
2
-
3
1
  module ReqresRspec
4
- module Collector
2
+ class Collector
3
+ # Contains spec values read from rspec example, request and response
4
+ attr_accessor :records
5
+
6
+ # response headers contain many unnecessary information,
7
+ # everything from this list will be stripped
8
+ EXCLUDE_RESPONSE_HEADER_PATTERNS = %w[
9
+ X-Frame-Options
10
+ X-XSS-Protection
11
+ X-Content-Type-Options
12
+ X-UA-Compatible
13
+ ]
14
+
15
+ # request headers contain many unnecessary information,
16
+ # everything that match items from this list will be stripped
17
+ EXCLUDE_REQUEST_HEADER_PATTERNS = %w[
18
+ rack.
19
+ action_dispatch
20
+ REQUEST_METHOD
21
+ SERVER_NAME
22
+ SERVER_PORT
23
+ QUERY_STRING
24
+ SCRIPT_NAME
25
+ CONTENT_LENGTH
26
+ HTTPS
27
+ HTTP_HOST
28
+ HTTP_USER_AGENT
29
+ REMOTE_ADDR
30
+ PATH_INFO
31
+ ]
32
+
33
+ def initialize
34
+ self.records = []
35
+ end
36
+
5
37
  # collects spec data for further processing
6
- def Collector.collect(spec, request, response)
7
- record = {
8
- title: 'title',
38
+ def collect(spec, request, response)
39
+ self.records << {
40
+ title: spec.example.full_description,
41
+ description: get_action_description(request.env['action_dispatch.request.parameters']['controller'], request.env['action_dispatch.request.parameters']['action']),
42
+ params: get_action_params(request.env['action_dispatch.request.parameters']['controller'], request.env['action_dispatch.request.parameters']['action']),
43
+ request_path: get_symbolized_path(request),
44
+ request: {
45
+ host: request.host,
46
+ url: request.url,
47
+ path: request.path,
48
+ method: request.request_method,
49
+ query_parameters: request.env['action_dispatch.request.parameters'].reject { |p| %w[controller action].include? p },
50
+ backend_parameters: request.env['action_dispatch.request.parameters'].reject { |p| !%w[controller action].include? p },
51
+ body: request.body.read,
52
+ content_length: request.content_length,
53
+ content_type: request.content_type,
54
+ headers: read_request_headers(request),
55
+ accept: request.accept,
56
+ },
57
+ response: {
58
+ code: response.status,
59
+ body: response.body,
60
+ headers: read_response_headers(response),
61
+ }
9
62
  }
63
+ end
64
+
65
+ private
66
+
67
+ # read and cleanup response headers
68
+ # returns Hash
69
+ def read_response_headers(response)
70
+ headers = response.headers
71
+ EXCLUDE_RESPONSE_HEADER_PATTERNS.each do |pattern|
72
+ headers = headers.reject { |h| h if h.starts_with? pattern }
73
+ end
74
+ headers
75
+ end
76
+
77
+ # read and cleanup request headers
78
+ # returns Hash
79
+ def read_request_headers(request)
80
+ headers = {}
81
+ request.env.keys.each do |key|
82
+ headers.merge!(key => request.env[key]) if EXCLUDE_REQUEST_HEADER_PATTERNS.all? { |p| !key.starts_with? p }
83
+ end
84
+ headers
85
+ end
86
+
87
+ # replace each first occurrence of param's value in the request path
88
+ #
89
+ # example
90
+ # request path = /api/users/123
91
+ # id = 123
92
+ # symbolized path => /api/users/:id
93
+ #
94
+ def get_symbolized_path(request)
95
+ request_path = request.path
96
+
97
+ request.env['action_dispatch.request.parameters'].
98
+ reject { |param| %w[controller action].include? param }.
99
+ each do |key, value|
100
+ if value.is_a? String
101
+ request_path = request_path.sub(value, ":#{key}") if request_path.index(value) >= 0
102
+ end
103
+ end
104
+
105
+ request_path
106
+ end
107
+
108
+ # returns action comments taken from controller file
109
+ # example TODO
110
+ def get_action_comments(controller, action)
111
+ lines = File.readlines(File.join(Rails.root, 'app', 'controllers', "#{controller}_controller.rb"))
112
+
113
+ action_line = nil
114
+ lines.each_with_index do |line, index|
115
+ if line.match /\s*def #{action}/ # def show
116
+ action_line = index
117
+ break
118
+ end
119
+ end
120
+
121
+ if action_line
122
+ comment_lines = []
123
+ was_comment = true
124
+ while action_line > 0 && was_comment
125
+ action_line -= 1
126
+
127
+ if lines[action_line].match /\s*#/
128
+ comment_lines << lines[action_line].strip
129
+ else
130
+ was_comment = false
131
+ end
132
+ end
133
+
134
+ comment_lines.reverse
135
+ else
136
+ 'not found'
137
+ end
138
+ end
139
+
140
+ # returns description action comments
141
+ # example TODO
142
+ def get_action_description(controller, action)
143
+ comment_lines = get_action_comments(controller, action)
144
+
145
+ description = []
146
+ comment_lines.each_with_index do |line, index|
147
+ if line.match /\s*#\s*@description/ # @description blah blah
148
+ description << line.gsub(/\A\s*#\s*@description/, '').strip
149
+ comment_lines[(index + 1)..-1].each do |multiline|
150
+ if !multiline.match /\s*#\s*@params/
151
+ description << multiline.gsub(/\A\s*#\s*/, '').strip
152
+ else
153
+ break
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ description.join ' '
160
+ end
161
+
162
+ # returns params action comments
163
+ # example TODO
164
+ def get_action_params(controller, action)
165
+ comment_lines = get_action_comments(controller, action)
166
+
167
+ text_params = []
168
+ last_new_param_index = nil
169
+ comment_lines.each_with_index do |line, index|
170
+ if line.match /\s*#\s*@params/ # @params id required Integer blah blah
171
+ last_new_param_index = index
172
+ text_params << line.gsub(/\A\s*#\s*@params/, '').strip
173
+ elsif last_new_param_index && last_new_param_index == index - 1
174
+ text_params.last << " #{line.gsub(/\A\s*#\s*/, '').strip}"
175
+ end
176
+ end
177
+
178
+ params = []
179
+
180
+ text_params.each do |param|
181
+ match_data = param.match /(?<name>[a-z0-9A-Z_\[\]]+)?\s*(?<required>required)?\s*(?<type>Integer|Boolean|String|Text|Float|Date|DateTime|File)?\s*(?<description>.*)/
182
+
183
+ if match_data
184
+ params << {
185
+ name: match_data[:name],
186
+ required: match_data[:required],
187
+ type: match_data[:type],
188
+ description: match_data[:description],
189
+ }
190
+ else
191
+ params << { description: param }
192
+ end
193
+ end
10
194
 
11
- ReqresRspec.records << record
195
+ params
12
196
  end
13
197
  end
14
198
  end
@@ -0,0 +1,24 @@
1
+ module ReqresRspec
2
+ module Generators
3
+ class Pdf
4
+ # generates PDF file from existing HTML docs
5
+ # TODO: more info
6
+ def generate
7
+ wkhtmltopdf_path = '/Applications/wkhtmltopdf.app/Contents/MacOS/wkhtmltopdf'
8
+ html_docs_root = File.join(Rails.root, 'docs')
9
+ pdf_doc_path = File.join(Rails.root, 'docs', "spec_#{Time.now.strftime("%d-%h-%Y_%H-%M")}.pdf")
10
+
11
+ if File.exists?(wkhtmltopdf_path)
12
+ files = Dir["#{html_docs_root}/rspec_docs_*.html"]
13
+ files_arg = files.map { |f| f if f =~ /\/rspec_docs_\d+\.html/ }.compact.sort.join(' ')
14
+
15
+ `#{wkhtmltopdf_path} #{files_arg} #{pdf_doc_path}`
16
+
17
+ puts "saved to #{pdf_doc_path}" if File.exists? pdf_doc_path
18
+ else
19
+ puts 'ERROR: wkhtmltopdf app not installed! Please check http://code.google.com/p/wkhtmltopdf/ for more info'
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,3 +1,3 @@
1
1
  module ReqresRspec
2
- VERSION = "0.0.1"
2
+ VERSION = '0.0.2'
3
3
  end
@@ -0,0 +1,55 @@
1
+ module ReqresRspec
2
+ module Writers
3
+ class Html
4
+ def initialize(records)
5
+ @records = records
6
+ end
7
+
8
+ def write
9
+ cleanup
10
+ generate_header
11
+ generate_specs
12
+ end
13
+
14
+ private
15
+
16
+ # deletes previous version of HTML docs
17
+ # TODO: more info
18
+ def cleanup
19
+ FileUtils.rm_rf(Dir.glob("#{Rails.root}/docs/rspec_docs_*.html"), secure: true)
20
+ end
21
+
22
+ # generates contents of HTML docs
23
+ # TODO: more info
24
+ def generate_header
25
+ tpl_path = File.join(File.dirname(__FILE__), 'templates', 'header.erb')
26
+ rendered_doc = ERB.new(File.open(tpl_path).read).result(binding)
27
+
28
+ path = File.join(Rails.root, 'docs', 'rspec_docs_00000.html')
29
+ file = File.open(path, 'w')
30
+ file.write(rendered_doc)
31
+ file.close
32
+ puts "Reqres::Writers::Html saved doc header to #{path}"
33
+ end
34
+
35
+ # generates each separate spec example doc
36
+ # TODO: more info
37
+ def generate_specs
38
+ tpl_path = File.join(File.dirname(__FILE__), 'templates', 'spec.erb')
39
+
40
+ @records.each_with_index do |record, index|
41
+ @record = record
42
+ @index = index + 1
43
+
44
+ rendered_doc = ERB.new(File.open(tpl_path).read).result(binding)
45
+
46
+ path = File.join(Rails.root, 'docs', "rspec_docs_#{('0000' + (@index).to_s)[-5, 5]}.html")
47
+ file = File.open(path, 'w')
48
+ file.write(rendered_doc)
49
+ file.close
50
+ puts "Reqres::Writers::Html saved doc spec to #{path}"
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,72 @@
1
+ <html>
2
+ <head>
3
+ <title><%= Rails.application.class.to_s.sub('::Application', '') %> API Documentation</title>
4
+ <link href='http://fonts.googleapis.com/css?family=Droid+Sans:400,700' rel='stylesheet' type='text/css'>
5
+ <link href='http://fonts.googleapis.com/css?family=Droid+Sans+Mono' rel='stylesheet' type='text/css'>
6
+ <style>
7
+ body {
8
+ color: #333333;
9
+ font-family: 'Verdana', 'Droid Sans', sans-serif;
10
+ font-size: 14px;
11
+ padding: 0;
12
+ margin: 0;
13
+ }
14
+ body *{
15
+ -webkit-box-sizing: border-box;
16
+ -moz-box-sizing: border-box;
17
+ box-sizing: border-box;
18
+ }
19
+ .container {
20
+ padding: 40px;
21
+ }
22
+ h1, h2, ul {
23
+ margin: 0 0 10px;
24
+ padding: 0;
25
+ }
26
+ h1 {
27
+ font-size: 2.286em;
28
+ }
29
+ h2 {
30
+ font-size: 2em;
31
+ }
32
+ a {
33
+ color: #1d96d2;
34
+ text-decoration: none;
35
+ }
36
+ ul {
37
+ padding-left: 0;
38
+ list-style: none;
39
+ }
40
+ li{
41
+ margin-bottom: 4px;
42
+ }
43
+
44
+ ol{
45
+ margin: 0 0 10px 0;
46
+ padding: 0 10px 0 50px;
47
+ }
48
+ ol li{
49
+ white-space: pre;
50
+ border-left: 1px solid #ccc;
51
+ margin: 0;
52
+ padding: 0 4px;
53
+ }
54
+ </style>
55
+ </head>
56
+ <body>
57
+ <div class="container">
58
+ <h1><%= Rails.application.class.to_s.sub('::Application', '') %> API Documentation</h1>
59
+ <p>Generated <%= Time.now.strftime('%d %B %Y at %H:%m:%S')%></p>
60
+ <ul>
61
+ <% @records.each_with_index do |record, index| %>
62
+ <li>
63
+ <%= index+1 %>.
64
+ <a href="rspec_docs_<%= ('0000' + (index + 1).to_s)[-5, 5] %>.html">
65
+ <%= record[:title] %>
66
+ </a>
67
+ </li>
68
+ <% end %>
69
+ </ul>
70
+ </div>
71
+ </body>
72
+ </html>
@@ -0,0 +1,213 @@
1
+ <html>
2
+ <head>
3
+ <title><%= Rails.application.class.to_s.sub('::Application', '') %> API Documentation</title>
4
+ <link href='http://fonts.googleapis.com/css?family=Droid+Sans:400,700' rel='stylesheet' type='text/css'>
5
+ <link href='http://fonts.googleapis.com/css?family=Droid+Sans+Mono' rel='stylesheet' type='text/css'>
6
+ <style>
7
+ body {
8
+ color: #333333;
9
+ font-family: 'Verdana', 'Droid Sans', sans-serif;
10
+ font-size: 14px;
11
+ padding: 0;
12
+ margin: 0;
13
+ }
14
+ body *{
15
+ -webkit-box-sizing: border-box;
16
+ -moz-box-sizing: border-box;
17
+ box-sizing: border-box;
18
+ }
19
+ .container {
20
+ padding: 40px;
21
+ }
22
+ h1, h2, ul {
23
+ margin: 0 0 10px;
24
+ padding: 0;
25
+ }
26
+ h1 {
27
+ font-size: 2.286em;
28
+ }
29
+ h2 {
30
+ font-size: 2em;
31
+ }
32
+ h3{
33
+ font-size: 1.57em;
34
+ margin: 14px 0 0px;
35
+ padding: 0;
36
+ }
37
+ h4{
38
+ font-size: 1.15em;
39
+ margin: 14px 0 0 0;
40
+ padding: 0;
41
+ }
42
+ h5{
43
+ font-size: 1em;
44
+ margin: 10px 0 0 0;
45
+ padding: 0;
46
+ }
47
+ a {
48
+ color: #1d96d2;
49
+ text-decoration: none;
50
+ }
51
+ .required {
52
+ color: #f00;
53
+ }
54
+ ul {
55
+ padding-left: 0;
56
+ list-style: none;
57
+ }
58
+ li{
59
+ margin-bottom: 4px;
60
+ }
61
+ .table{
62
+ border: 1px solid #cccccc;
63
+ border-radius: 4px;
64
+ overflow: hidden;
65
+ }
66
+ table{
67
+ width: 100%;
68
+ font-size: 1em;
69
+ padding: 0;
70
+ margin: 0;
71
+ border-collapse: collapse;
72
+ border-spacing: 0px;
73
+ }
74
+ tr{
75
+ border-top: 1px solid #cccccc;
76
+ }
77
+ tr:first-child{
78
+ border-top: 0
79
+ }
80
+ th, td{
81
+ margin: 0;
82
+ padding: 5px 8px;
83
+ text-align: left;
84
+ border-left: 1px solid #cccccc;
85
+ font-size: 1em;
86
+ }
87
+ th:first-child, td:first-child{
88
+ border-left: 0;
89
+ }
90
+ td{
91
+ background: #f9f9f9;
92
+ }
93
+ tr:nth-child(2n+1) td{
94
+ background: #ffffff;
95
+ }
96
+ td i{
97
+ font-style: normal;
98
+ font-weight: 300;
99
+ float: right;
100
+ }
101
+
102
+ code, ol{
103
+ display: block;
104
+ border-radius: 6px;
105
+ border: 1px solid #cccccc;
106
+ width: 100%;
107
+ background: #f5f5f5;
108
+ font-family: 'Courier New','Droid Sans Mono', monospace;
109
+ font-size: 0.85em;
110
+ font-weight: 700;
111
+ }
112
+ code{
113
+ white-space: pre-line;
114
+ padding: 10px;
115
+ font-size: 0.85em;
116
+ }
117
+ code i{
118
+ font-weight: 400;
119
+ font-style: normal;
120
+ }
121
+ ol{
122
+ margin: 0 0 10px 0;
123
+ padding: 0 10px 0 50px;
124
+ }
125
+ ol li{
126
+ white-space: pre;
127
+ border-left: 1px solid #ccc;
128
+ margin: 0;
129
+ padding: 0 4px;
130
+ }
131
+ </style>
132
+ </head>
133
+ <body>
134
+ <div class="container">
135
+ <h3>
136
+ <a href="rspec_docs_00000.html">&#9650</a>
137
+ <a id="<%= @index %>"></a><%= @index %>. <%= @record[:title] %>
138
+ </h3>
139
+
140
+ <p><%= @record[:description] %></p>
141
+
142
+ <code><%= @record[:request][:method] %> <%= @record[:request_path] %></code>
143
+
144
+ <% if @record[:params].size > 0 %>
145
+ <h5>Parameters</h5>
146
+ <div class="table">
147
+ <table>
148
+ <tr>
149
+ <th>Name</th>
150
+ <th>Type</th>
151
+ <th>Description</th>
152
+ </tr>
153
+ <% @record[:params].each do |param| %>
154
+ <tr>
155
+ <td><%= param[:name] %> <i class="required"><%= param[:required] %></i></td>
156
+ <td><%= param[:type] %></td>
157
+ <td><%= param[:description] %></td>
158
+ </tr>
159
+ <% end %>
160
+ </table>
161
+ </div>
162
+ <% end %>
163
+
164
+ <h4>Request</h4>
165
+
166
+ <% if @record[:request][:headers].keys.count > 0 %>
167
+ <h5>Headers</h5>
168
+ <code><%= @record[:request][:headers].collect { |k, v| "#{k}: <i>#{v}</i>"}.join("\n")%></code>
169
+ <% end %>
170
+
171
+ <h5>Route</h5>
172
+ <code><%= @record[:request][:method] %> <%= @record[:request][:path] %></code>
173
+
174
+ <h5>Query parameters</h5>
175
+ <code><%= @record[:request][:query_parameters] %></code>
176
+
177
+ <h5><a href="http://curl.haxx.se/docs/manpage.html">CURL</a> Example</h5>
178
+ <code>TODO</code>
179
+
180
+ <% if @record[:request][:body].present? %>
181
+ <h5>Body</h5>
182
+ <ol>
183
+ <% @record[:request][:body].split("\n").each do |line| %>
184
+ <li><%= line %></li>
185
+ <% end %>
186
+ </ol>
187
+ <% end %>
188
+
189
+ <h4>Response</h4>
190
+
191
+ <% if @record[:response][:headers].count > 0 %>
192
+ <h5>Headers</h5>
193
+ <code><%= @record[:response][:headers].collect { |k, v| "#{k}: <i>#{v}</i>"}.join("\n")%></code>
194
+ <% end %>
195
+
196
+ <h5>Status code (<a href="http://en.wikipedia.org/wiki/List_of_HTTP_status_codes">wiki</a>)</h5>
197
+ <code>HTTP <%= @record[:response][:code] %></code>
198
+
199
+ <% if @record[:response][:body].present? %>
200
+ <h5>Body</h5>
201
+ <ol>
202
+ <% JSON.pretty_generate(
203
+ JSON.parse(
204
+ @record[:response][:body]
205
+ )
206
+ ).split("\n").each do |line| %>
207
+ <li><%= line %></li>
208
+ <% end %>
209
+ </ol>
210
+ <% end %>
211
+ </div>
212
+ </body>
213
+ </html>
data/lib/reqres_rspec.rb CHANGED
@@ -1,41 +1,27 @@
1
- # to enable mattr_accessor
2
- require "active_support/core_ext/module/attribute_accessors"
1
+ require 'reqres_rspec/version'
2
+ require 'reqres_rspec/collector'
3
+ require 'reqres_rspec/writers/html'
4
+ require 'reqres_rspec/generators/pdf'
3
5
 
4
- require "reqres_rspec/version"
5
- require "reqres_rspec/collector"
6
-
7
- module ReqresRspec
8
- # Contains spec values read from rspec example, request and response
9
- mattr_accessor :records
10
-
11
- # define if all doc generation is enabled
12
- mattr_accessor :enabled
13
- end
14
-
15
- ReqresRspec.enabled = defined?(RSpec)
16
-
17
- if !ReqresRspec.enabled
18
- puts "\nWARNING: ReqresRspec is disabled\n"
19
- end
20
-
21
- if ReqresRspec.enabled
22
- # initialize
23
- ReqresRspec.records = []
6
+ if defined?(RSpec) && ENV['REQRES_RSPEC'] == '1'
7
+ collector = ReqresRspec::Collector.new
24
8
 
25
9
  RSpec.configure do |config|
26
10
  config.after(:each) do
27
11
  if defined?(request) && defined?(response)
28
12
  unless self.example.options.has_key?(:rspec_doc) && !self.example.options[:rspec_doc]
29
- ReqresRspec::Collector.collect(self, request, response)
13
+ collector.collect(self, request, response)
30
14
  end
31
15
  end
32
16
  end
33
17
 
34
18
  config.after(:suite) do
35
- if ReqresRspec.records && ReqresRspec.records.size > 0
36
- puts 'TODO: save records'
37
- puts ReqresRspec.records.inspect
19
+ if collector.records.size > 0
20
+ ReqresRspec::Writers::Html.new(collector.records).write
21
+ ReqresRspec::Generators::Pdf.new.generate
38
22
  end
39
23
  end
40
24
  end
25
+ else
26
+ puts "\nNOTICE: ReqresRspec is disabled. run RSpec with REQRES_RSPEC=1 environment var\n"
41
27
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reqres_rspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - rilian
@@ -53,7 +53,11 @@ files:
53
53
  - Rakefile
54
54
  - lib/reqres_rspec.rb
55
55
  - lib/reqres_rspec/collector.rb
56
+ - lib/reqres_rspec/generators/pdf.rb
56
57
  - lib/reqres_rspec/version.rb
58
+ - lib/reqres_rspec/writers/html.rb
59
+ - lib/reqres_rspec/writers/templates/header.erb
60
+ - lib/reqres_rspec/writers/templates/spec.erb
57
61
  - reqres_rspec.gemspec
58
62
  homepage: ''
59
63
  licenses: