goodall 0.0.1
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.
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/.rvmrc +2 -0
- data/.travis.yml +10 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +311 -0
- data/Rakefile +11 -0
- data/goodall.gemspec +27 -0
- data/lib/goodall.rb +142 -0
- data/lib/goodall/command_line_enable.rb +8 -0
- data/lib/goodall/cucumber.rb +13 -0
- data/lib/goodall/errors.rb +4 -0
- data/lib/goodall/handler/base.rb +9 -0
- data/lib/goodall/handler/json.rb +64 -0
- data/lib/goodall/handler/xml.rb +26 -0
- data/lib/goodall/rake_task.rb +11 -0
- data/lib/goodall/rspec.rb +2 -0
- data/lib/goodall/version.rb +3 -0
- data/lib/goodall/writer.rb +15 -0
- data/lib/tasks/goodall.rake +60 -0
- data/spec/lib/goodall/handler/json_spec.rb +41 -0
- data/spec/lib/goodall_spec.rb +209 -0
- data/spec/spec_helper.rb +5 -0
- metadata +151 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rvmrc
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Matthew Nielsen
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,311 @@
|
|
1
|
+
[](https://travis-ci.org/xunker/Goodall)
|
2
|
+
# Goodall
|
3
|
+
|
4
|
+
Goodall provides an easy interface for documenting your API while you
|
5
|
+
write your integration tests.
|
6
|
+
|
7
|
+
It is compatible with Rspec, Cucumber and test-unit, as well as others. It
|
8
|
+
currently supports JSON and XML APIs, but modules can be easily written to
|
9
|
+
handler other types.
|
10
|
+
|
11
|
+
Goodall is named after researcher Jane Goodall, who has spent her life observing and
|
12
|
+
documenting the behviour of chimpanzees.
|
13
|
+
|
14
|
+
## Purpose
|
15
|
+
|
16
|
+
Goodall is a gemified version of the ideas in outlined a few years ago in
|
17
|
+
this blog post: http://n4k3d.com/blog/2011/08/19/generate-easy-up-to-date-rails-api-docs-with-cucumber/
|
18
|
+
|
19
|
+
The basic idea is that you are writing integration tests anyway, you are 90%
|
20
|
+
there. All that was left was to record the requests and responses to a text
|
21
|
+
file. For example, the following cucumber test:
|
22
|
+
|
23
|
+
```cucumber
|
24
|
+
Feature: /widgets
|
25
|
+
Scenario: Get a list of all widgets
|
26
|
+
Given the following widget exists:
|
27
|
+
| name | job |
|
28
|
+
| Billy the Widget | Be a widget |
|
29
|
+
| Sally the Widget | Be a widget |
|
30
|
+
When I GET "/widgets"
|
31
|
+
Then I should get a successful response
|
32
|
+
And the response should include 2 widgets
|
33
|
+
And the response should include the Widget data for "Billy the Widget"
|
34
|
+
And the response should include the Widget data for "Sally the Widget"
|
35
|
+
|
36
|
+
Scenario: Create a new widget
|
37
|
+
Given I have a JSON request for "widget"
|
38
|
+
| name | Bob the Widget |
|
39
|
+
| job | Be a widget |
|
40
|
+
And I POST that JSON to "/widgets.json"
|
41
|
+
Then I should get a successful response
|
42
|
+
And the response should include the Widget data for "Bob the Widget"
|
43
|
+
```
|
44
|
+
|
45
|
+
.. would then generate the following documentation:
|
46
|
+
|
47
|
+
```
|
48
|
+
——————————————————————————–
|
49
|
+
Feature: /widgets
|
50
|
+
|
51
|
+
Scenario: Get a list of all widgets
|
52
|
+
GET /widgets.json
|
53
|
+
RESPONSE:
|
54
|
+
{
|
55
|
+
"widgets" : [
|
56
|
+
{
|
57
|
+
"name" : "Billy the Widget",
|
58
|
+
"job" : "Be a widget"
|
59
|
+
},
|
60
|
+
{
|
61
|
+
"name" : "Sally the Widget",
|
62
|
+
"job" : "Be a widget"
|
63
|
+
}
|
64
|
+
]
|
65
|
+
}
|
66
|
+
|
67
|
+
Scenario: Create a new widget
|
68
|
+
POST /widgets.json:
|
69
|
+
{
|
70
|
+
"widget" : {
|
71
|
+
"name" : "Bob the Widget",
|
72
|
+
"job" : "Be a widget"
|
73
|
+
}
|
74
|
+
}
|
75
|
+
RESPONSE:
|
76
|
+
{
|
77
|
+
"widget" : {
|
78
|
+
"name" : "Billy the Widget",
|
79
|
+
"job" : "Be a widget"
|
80
|
+
}
|
81
|
+
}
|
82
|
+
```
|
83
|
+
|
84
|
+
## Installation
|
85
|
+
|
86
|
+
Add this line to your application's Gemfile:
|
87
|
+
|
88
|
+
gem 'goodall'
|
89
|
+
|
90
|
+
And then execute:
|
91
|
+
|
92
|
+
$ bundle
|
93
|
+
|
94
|
+
Or install it yourself as:
|
95
|
+
|
96
|
+
$ gem install goodall
|
97
|
+
|
98
|
+
## Usage
|
99
|
+
|
100
|
+
### Cucumber
|
101
|
+
|
102
|
+
In your *env.rv* file, require 'goodall/cucumber' and either of the JSON
|
103
|
+
or XML handlers. Or both, if you need them.
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
# env.rb
|
107
|
+
require 'goodall/cucumber'
|
108
|
+
require 'goodall/handler/json' # or/and 'goodall/handler/xml'
|
109
|
+
```
|
110
|
+
|
111
|
+
In your features, where you want to log a request or response, call either
|
112
|
+
*Goodall.document_request* or *Goodall.document_response*.
|
113
|
+
|
114
|
+
For cucumber, the best way is to have often-reused methods in each scenario, and then put the Goodall methods call in those. Take this example feature:
|
115
|
+
|
116
|
+
```Cucumber
|
117
|
+
# something.feature
|
118
|
+
Given I get "/some/api.json"
|
119
|
+
Then the response should be valid json
|
120
|
+
And the response should should be successful
|
121
|
+
|
122
|
+
# something_steps.rb
|
123
|
+
|
124
|
+
Given /^I get \"(.+)\"$/ do |path|
|
125
|
+
get(path)
|
126
|
+
Goodall.document_request(:get, path)
|
127
|
+
end
|
128
|
+
|
129
|
+
Given /^the response should be valid json$/ do
|
130
|
+
Goodall.document_response(last_response.body)
|
131
|
+
@json_response = MultiJson.load(last_response.body)
|
132
|
+
end
|
133
|
+
|
134
|
+
Given /^the response should be successul$/ do
|
135
|
+
@last_response['success'].should be_true
|
136
|
+
end
|
137
|
+
```
|
138
|
+
|
139
|
+
The steps "I get (.+)" and "the response should be valid json" are steps I
|
140
|
+
will use in almost every scenario, so it is the perfect place for the Goodall
|
141
|
+
calls. Using Goodall in these frequently used steps is key for ease-of-use.
|
142
|
+
|
143
|
+
_IMPORTANT NOTE_: Using the cucumber helpers, Goodall will NOT log unless
|
144
|
+
executed via the rake task "rake cucumber:document". To force Goodall to
|
145
|
+
log, please set the environment variable 'ENABLE_GOODALL' to a non-nil
|
146
|
+
value. For example:
|
147
|
+
|
148
|
+
```
|
149
|
+
ENABLE_GOODALL=true cucumber features/
|
150
|
+
```
|
151
|
+
|
152
|
+
### Rspec
|
153
|
+
|
154
|
+
In your *spec_helper.rb* file, require 'goodall/rspec' and either of the
|
155
|
+
JSON or XML handlers. Or both, if you need them.
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
# spec_helper.rb
|
159
|
+
require 'goodall/rspec'
|
160
|
+
require 'goodall/handler/json' # or/and 'goodall/handler/xml'
|
161
|
+
```
|
162
|
+
|
163
|
+
For controller specs, re-use can be acheived by making a delegation method
|
164
|
+
for the get/post/put/delete/patch verbs:
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
# spec_helper.rb
|
168
|
+
|
169
|
+
def documented_get(*args)
|
170
|
+
Goodall.document_request(:get, args[0])
|
171
|
+
get_response = get(args)
|
172
|
+
Goodall.document_response(response.body)
|
173
|
+
get_response
|
174
|
+
end
|
175
|
+
|
176
|
+
# some_controller_spec.rb
|
177
|
+
describe SomeController do
|
178
|
+
describe "GET foo" do
|
179
|
+
it "should return a body containing 'blah'" do
|
180
|
+
|
181
|
+
documented_get(:foo) # replaces get(:foo)
|
182
|
+
expect(response.body).to include?('blah')
|
183
|
+
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
```
|
188
|
+
|
189
|
+
_IMPORTANT NOTE_: Using the rspec helpers, Goodall will NOT log unless
|
190
|
+
executed via the rake task "rake rspec:document". To force Goodall to
|
191
|
+
log, please set the environment variable 'ENABLE_GOODALL' to a non-nil
|
192
|
+
value. For example:
|
193
|
+
|
194
|
+
```shell
|
195
|
+
ENABLE_GOODALL=true rspec spec/
|
196
|
+
```
|
197
|
+
|
198
|
+
### Test Unit and derivatives, and others
|
199
|
+
|
200
|
+
If you are not using any of the convenience wrappers for rspec or cucumber,
|
201
|
+
there is more work to be done when using Goodall.
|
202
|
+
|
203
|
+
First, like others, require the files:
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
require 'goodall'
|
207
|
+
require 'goodall/handler/json' # or/and 'goodall/handler/xml'
|
208
|
+
```
|
209
|
+
|
210
|
+
You will also need to set the path of the file for output. If you choose
|
211
|
+
not the set this, the default will be used which is "./api_docs.txt".
|
212
|
+
You can override this by:
|
213
|
+
|
214
|
+
```ruby
|
215
|
+
Goodall.output_path = "./some/path/file.txt"
|
216
|
+
```
|
217
|
+
|
218
|
+
Enabing the logging process involves setting the *enabled* property. It is
|
219
|
+
disabled by default, which means that Goodall will not document unless
|
220
|
+
explicity told to do so. You can enable logging with
|
221
|
+
|
222
|
+
```ruby
|
223
|
+
Goodall.enabled = true
|
224
|
+
```
|
225
|
+
|
226
|
+
### Rake task
|
227
|
+
|
228
|
+
To automate the creation of documentation, a rake task can be added that
|
229
|
+
automatically set the output file and triggers the tests.
|
230
|
+
|
231
|
+
```ruby
|
232
|
+
# Rakefile
|
233
|
+
require 'goodall/rake_task'
|
234
|
+
```
|
235
|
+
|
236
|
+
This unlocks the following rake commands:
|
237
|
+
|
238
|
+
```shell
|
239
|
+
rake cucumber:document # Run cucumber and write Goodall documentation
|
240
|
+
rake spec:document # Run rspec tests and write Goodall documentation
|
241
|
+
rake goodall:output_path # Show current Goodall documentation output path
|
242
|
+
```
|
243
|
+
|
244
|
+
By default, the output path will be 'doc/api_docs.txt' when run with this
|
245
|
+
rake task.
|
246
|
+
|
247
|
+
## Handlers
|
248
|
+
|
249
|
+
By default, Goodall includes handlers for XML and JSON. This means that the
|
250
|
+
logged output can be parsed from these formats and pretty-printed correctly
|
251
|
+
to the documention file.
|
252
|
+
|
253
|
+
If only one handler is required in your config, Goodall assumes you want to
|
254
|
+
use that one all the time. For example:
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
require 'goodall'
|
258
|
+
require 'goodall/handler/json'
|
259
|
+
|
260
|
+
Goodall.document_reponse(response.body)
|
261
|
+
```
|
262
|
+
|
263
|
+
..will assume *response.body* is JSON and will parse it as such. If you were
|
264
|
+
to swap "goodall/handler/json" with "goodall/handler/xml" then the response
|
265
|
+
would be assumed to be XML.
|
266
|
+
|
267
|
+
If you need to use both formats at the same time, you can include both
|
268
|
+
handlers and set the actuve handler by name.
|
269
|
+
|
270
|
+
```ruby
|
271
|
+
require 'goodall'
|
272
|
+
require 'goodall/handler/json'
|
273
|
+
require 'goodall/handler/xml'
|
274
|
+
|
275
|
+
Goodall.set_handler(:json)
|
276
|
+
Goodall.document_response(response.body) # json response
|
277
|
+
|
278
|
+
Goodall.set_handler(:xml)
|
279
|
+
Goodall.document_response(response.body) # xml response
|
280
|
+
```
|
281
|
+
|
282
|
+
In the above case, the default handler would be XML since it was required
|
283
|
+
last.
|
284
|
+
|
285
|
+
### Writing new handlers
|
286
|
+
|
287
|
+
Please see *lib/goodall/handler/json.rb* for a good example. A handler will
|
288
|
+
need to do two things:
|
289
|
+
|
290
|
+
**Implement #parse_payload**: accepts a data structure and is expected to
|
291
|
+
return a pretty-printed string representing that data.
|
292
|
+
|
293
|
+
**Register itself as a handler**: This is done by calling:
|
294
|
+
|
295
|
+
```ruby
|
296
|
+
Goodall.register_handler(:handler_name, self)
|
297
|
+
```
|
298
|
+
..where *:handler_name* is a **symbol** for what type of data is being
|
299
|
+
handled, and self is the **class** of the handler.
|
300
|
+
|
301
|
+
## Methods
|
302
|
+
|
303
|
+
Documented with rdoc.
|
304
|
+
|
305
|
+
## Contributing
|
306
|
+
|
307
|
+
1. Fork it
|
308
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
309
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
310
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
311
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/goodall.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'goodall/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "goodall"
|
8
|
+
spec.version = Goodall::VERSION
|
9
|
+
spec.authors = ["Matthew Nielsen"]
|
10
|
+
spec.email = ["xunker@pyxidis.org"]
|
11
|
+
spec.description = %q{An easy interface for documenting your API while you
|
12
|
+
write your tests.}
|
13
|
+
spec.summary = %q{Goodall provides an easy interface for documenting your API while you write your tests. It is compatible with Rspec, Cucumber and test-unit, as well as others. Goodall is named after Jane Goodall who has spent her life observing and documenting the behviour of chimpanzees.}
|
14
|
+
spec.homepage = "http://github.com/xunker/goodall"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files`.split($/)
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency "multi_json", ">= 1.6"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "rspec", ">= 2.10"
|
27
|
+
end
|
data/lib/goodall.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require "goodall/version"
|
3
|
+
require "goodall/errors"
|
4
|
+
require "goodall/writer"
|
5
|
+
|
6
|
+
require 'goodall/rake_task' if defined?(Rails)
|
7
|
+
|
8
|
+
class Goodall
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
@@enabled = false #:nodoc:
|
12
|
+
@@output_path = './api_docs.txt' #:nodoc:
|
13
|
+
@@registered_handlers = {} #:nodoc:
|
14
|
+
|
15
|
+
# Get the current documentation output path
|
16
|
+
def self.output_path
|
17
|
+
@@output_path
|
18
|
+
end
|
19
|
+
|
20
|
+
# Set the current documentation output path
|
21
|
+
def self.output_path=(val)
|
22
|
+
@@output_path = val
|
23
|
+
end
|
24
|
+
|
25
|
+
# Is Goodall logging enabled?
|
26
|
+
def self.enabled
|
27
|
+
@@enabled
|
28
|
+
end
|
29
|
+
|
30
|
+
# alias unreliable on class methods, use this instead.
|
31
|
+
def self.enabled?
|
32
|
+
enabled
|
33
|
+
end
|
34
|
+
|
35
|
+
# Explicity set the enabled state, true or false
|
36
|
+
def self.enabled=(val)
|
37
|
+
@@enabled=!!val
|
38
|
+
end
|
39
|
+
|
40
|
+
# Enable Goodall, which is disabled by default.
|
41
|
+
def self.enable
|
42
|
+
self.enabled=true
|
43
|
+
end
|
44
|
+
|
45
|
+
# Enable Goodall, which is the default by default.
|
46
|
+
def self.disable
|
47
|
+
self.enabled = false
|
48
|
+
end
|
49
|
+
|
50
|
+
# write to the currently open output file
|
51
|
+
def self.write(str)
|
52
|
+
writer.write(str) if enabled?
|
53
|
+
end
|
54
|
+
|
55
|
+
# Document a request.
|
56
|
+
#
|
57
|
+
# * +:method+ - a symbol of the verb: :get, :post, :put, :delete, :patch
|
58
|
+
# * +:path+ - a string of the path (URL/URI) of the request
|
59
|
+
# * +:payload+ - the parameters sent, e.g. post body. Usually a hash.
|
60
|
+
def self.document_request(method, path, payload=nil)
|
61
|
+
return unless enabled?
|
62
|
+
|
63
|
+
str = "#{method.to_s.upcase}: #{path}"
|
64
|
+
|
65
|
+
if payload && payload.to_s.size > 0
|
66
|
+
str << "\n" + current_handler.parse_payload(payload)
|
67
|
+
end
|
68
|
+
|
69
|
+
str << "\n"
|
70
|
+
|
71
|
+
writer.write(str)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Document a response.
|
75
|
+
#
|
76
|
+
# * +:payload - the data returned from the request, e.g. response.body. `payload` will be run through the current handler and be pretty-printed to the output file.
|
77
|
+
def self.document_response(payload)
|
78
|
+
return unless enabled?
|
79
|
+
|
80
|
+
if payload
|
81
|
+
payload = current_handler.parse_payload(payload)
|
82
|
+
end
|
83
|
+
|
84
|
+
str = "RESPONSE:\n#{payload}\n"
|
85
|
+
|
86
|
+
writer.write(str)
|
87
|
+
end
|
88
|
+
|
89
|
+
at_exit do
|
90
|
+
if enabled? && writer
|
91
|
+
writer.close
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# When writing a custom hander, it must register itself with Goodall using
|
96
|
+
# this method.
|
97
|
+
#
|
98
|
+
# * +:payload_type+ - The name of the kind of content that this handler will be processing, e.g. JSON, XML, HTML etc.
|
99
|
+
# * +:handler_class+ - The class of the handler itself (not the class name).
|
100
|
+
def self.register_handler(payload_type, handler_class)
|
101
|
+
@@registered_handlers[payload_type.to_sym] = handler_class
|
102
|
+
end
|
103
|
+
|
104
|
+
# Set the currently active handler. By default, if only one handler is
|
105
|
+
# registered then it will be made active by default. If you hanve multiple
|
106
|
+
# handlers registered and wish to switch between them, use this.
|
107
|
+
#
|
108
|
+
# * +:handler_name+ - Handler name as a symbol, e.g. :json, :xml.
|
109
|
+
def self.set_handler(handler_name)
|
110
|
+
handler_name = handler_name.to_sym
|
111
|
+
if handler_class = @@registered_handlers[handler_name]
|
112
|
+
@current_handler = handler_class.new
|
113
|
+
else
|
114
|
+
raise HandlerNotRegisteredError, "No handler registered for for #{handler_name}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# returns an array of arrays of the currently registered handlers.
|
119
|
+
#
|
120
|
+
# [ [ :identifier, class ], [ :identifier, class ] ]
|
121
|
+
def self.registered_handlers
|
122
|
+
@@registered_handlers.map{|k,v| [k,v]}
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def self.current_handler
|
128
|
+
@current_handler ||= if default_handler = @@registered_handlers.first
|
129
|
+
default_handler[1].new
|
130
|
+
else
|
131
|
+
raise(
|
132
|
+
Goodall::NoHandlersRegisteredError,
|
133
|
+
"There are no handlers registered, please require at least one."
|
134
|
+
)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.writer
|
139
|
+
@writer ||= Goodall::Writer.new(@@output_path)
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'goodall'
|
2
|
+
require 'goodall/command_line_enable'
|
3
|
+
|
4
|
+
Before do |scenario|
|
5
|
+
if scenario.feature != $current_feature
|
6
|
+
$current_feature = scenario.feature
|
7
|
+
Goodall.write("\n")
|
8
|
+
Goodall.write("#{'-'*80}\nFeature: #{$current_feature.name}")
|
9
|
+
end
|
10
|
+
Goodall.write("\n")
|
11
|
+
Goodall.write("Scenario: #{scenario.name}")
|
12
|
+
Goodall.write("\n")
|
13
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
require "goodall/handler/base"
|
3
|
+
|
4
|
+
class Goodall
|
5
|
+
module Handler
|
6
|
+
class Json < Base
|
7
|
+
Goodall.register_handler :json, self
|
8
|
+
|
9
|
+
def parse_payload(payload)
|
10
|
+
payload = if payload.class == String
|
11
|
+
# assue it's a string of json
|
12
|
+
begin
|
13
|
+
MultiJson.load(payload)
|
14
|
+
rescue MultiJson::LoadError
|
15
|
+
# probably not JSON, return as-is
|
16
|
+
return payload+"\n"
|
17
|
+
end
|
18
|
+
else
|
19
|
+
payload
|
20
|
+
end
|
21
|
+
|
22
|
+
# detect "pretty" json by seeing if there are CRs in here
|
23
|
+
if (json = MultiJson.dump(payload)) =~ /\n/
|
24
|
+
json
|
25
|
+
else
|
26
|
+
pretty_print(json)
|
27
|
+
# json
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# We're doing this outselves because it's too unreliable detecting which parsers support pretty-print and whoch one don't. If this method is broken, at least it will be *consitently* broken.
|
35
|
+
def pretty_print(json)
|
36
|
+
return json if json.to_s.size < 1
|
37
|
+
|
38
|
+
str = json.to_s.gsub("},", "},\n").gsub("],", "],\n").gsub("{[", "{\n[").gsub("}]", "}\n]").gsub("[{", "[\n{").gsub("]}", "]\n}").gsub("{\"", "{\n\"").gsub("\"}", "\"\n}").gsub("\",\"", "\",\n\"")
|
39
|
+
|
40
|
+
if str.match(/[^\n]\}$/)
|
41
|
+
str.gsub!(/\}$/, "\n}")
|
42
|
+
end
|
43
|
+
|
44
|
+
output = []
|
45
|
+
|
46
|
+
indent_level = 0
|
47
|
+
str.split("\n").each do |s|
|
48
|
+
indent_level -= 1 if ["]", "}"].include?(s.split('').first) && indent_level > 0
|
49
|
+
output << (" "*indent_level) + s
|
50
|
+
if ["{", "["].include?(s.split('').last)
|
51
|
+
indent_level += 1
|
52
|
+
next
|
53
|
+
end
|
54
|
+
|
55
|
+
if ["{", "["].include?(s.split('').first)
|
56
|
+
indent_level += 1
|
57
|
+
next
|
58
|
+
end
|
59
|
+
end
|
60
|
+
output.join("\n")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "goodall/handler/base"
|
2
|
+
|
3
|
+
# This sucks.
|
4
|
+
# It needs love from someone who knows xml. That's not me.
|
5
|
+
|
6
|
+
class Goodall
|
7
|
+
module Handler
|
8
|
+
class Xml < Base
|
9
|
+
Goodall.register_handler :xml, self
|
10
|
+
|
11
|
+
def parse_payload(payload)
|
12
|
+
if payload.class == String
|
13
|
+
# assue it's a string of xml
|
14
|
+
return payload
|
15
|
+
else
|
16
|
+
begin
|
17
|
+
return payload.to_xml
|
18
|
+
rescue Exception => e
|
19
|
+
puts "!!! Just tried to call to_xml on your response, but an error was returned. Your object may not support this."
|
20
|
+
raise e
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
def goodall_installed?
|
2
|
+
begin
|
3
|
+
gem 'goodall'
|
4
|
+
rescue Gem::LoadError
|
5
|
+
false
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def running_under_rails?
|
10
|
+
defined?(Rails)
|
11
|
+
end
|
12
|
+
|
13
|
+
def goodall_not_installed
|
14
|
+
raise "the Goodall gem is not installed or not enabled. `bundle exec` may fix this."
|
15
|
+
end
|
16
|
+
|
17
|
+
def goodall_output_path
|
18
|
+
if running_under_rails?
|
19
|
+
"#{Rails.root}/doc/api_docs.txt"
|
20
|
+
else
|
21
|
+
Goodall.output_path
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def set_goodall_putput_path
|
26
|
+
ENV['GOODALL_OUTPUT_PATH'] = goodall_output_path
|
27
|
+
end
|
28
|
+
|
29
|
+
def enable_goodall
|
30
|
+
ENV['ENABLE_GOODALL'] = 'true'
|
31
|
+
end
|
32
|
+
|
33
|
+
namespace :cucumber do
|
34
|
+
desc "Run cucumber and write Goodall documentation"
|
35
|
+
goodall_not_installed unless goodall_installed?
|
36
|
+
task :document => :environment do
|
37
|
+
set_goodall_putput_path
|
38
|
+
enable_goodall
|
39
|
+
Rake::Task["cucumber"].invoke
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
namespace :spec do
|
44
|
+
desc "Run rspec tests and write Goodall documentation"
|
45
|
+
goodall_not_installed unless goodall_installed?
|
46
|
+
task :document => :environment do
|
47
|
+
set_goodall_putput_path
|
48
|
+
enable_goodall
|
49
|
+
Rake::Task["spec"].invoke
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
namespace :goodall do
|
54
|
+
desc "Show current Goodall documentation output path"
|
55
|
+
goodall_not_installed unless goodall_installed?
|
56
|
+
task :output_path => :environment do
|
57
|
+
puts "Goodall output path: #{goodall_output_path}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'goodall'
|
3
|
+
require 'goodall/handler/json'
|
4
|
+
|
5
|
+
describe Goodall::Handler::Json do
|
6
|
+
describe '#parse_payload' do
|
7
|
+
context 'payload is a string' do
|
8
|
+
context 'string is valid json' do
|
9
|
+
|
10
|
+
let(:valid_json_string) { '{"foo":"bar"}' }
|
11
|
+
|
12
|
+
it 'should return it as pretty-printed json' do
|
13
|
+
expect(
|
14
|
+
subject.parse_payload(valid_json_string)
|
15
|
+
).to eq("{\n \"foo\":\"bar\"\n}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
context 'the string not valid json' do
|
19
|
+
|
20
|
+
let(:invalid_json_string) { 'BLAHBLAH' }
|
21
|
+
|
22
|
+
it 'should return the string with CR added' do
|
23
|
+
expect(
|
24
|
+
subject.parse_payload(invalid_json_string)
|
25
|
+
).to eq("#{invalid_json_string}\n")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'payload is not a string' do
|
31
|
+
|
32
|
+
let(:payload) { { :foo => :bar } }
|
33
|
+
|
34
|
+
it 'should return it as pretty-printed json' do
|
35
|
+
expect(
|
36
|
+
subject.parse_payload(payload)
|
37
|
+
).to eq("{\n \"foo\":\"bar\"\n}")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'goodall'
|
3
|
+
|
4
|
+
describe Goodall do
|
5
|
+
let(:klass) { Goodall }
|
6
|
+
|
7
|
+
let(:mock_writer) { double(:writer, :close => nil) }
|
8
|
+
|
9
|
+
let(:mock_handler) { double(:mock_handler) }
|
10
|
+
|
11
|
+
let(:mock_new_handler) { double(:mock_new_handler) }
|
12
|
+
|
13
|
+
before(:each) do
|
14
|
+
Goodall.stub(:writer).and_return(mock_writer)
|
15
|
+
end
|
16
|
+
|
17
|
+
after(:each) do
|
18
|
+
Goodall.disable
|
19
|
+
end
|
20
|
+
|
21
|
+
describe ".output_path" do
|
22
|
+
it "must return the current output file path" do
|
23
|
+
# default
|
24
|
+
expect(klass.output_path).to eq('./api_docs.txt')
|
25
|
+
|
26
|
+
# setting
|
27
|
+
klass.output_path = 'foo/bar.txt'
|
28
|
+
expect(klass.output_path).to eq('foo/bar.txt')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe ".enabled" do
|
33
|
+
it "must be true if goodall is enabled" do
|
34
|
+
klass.enable
|
35
|
+
|
36
|
+
expect(klass.enabled).to be_true
|
37
|
+
end
|
38
|
+
it "must be false if goodall is not enabled" do
|
39
|
+
klass.disable
|
40
|
+
|
41
|
+
expect(klass.enabled).to be_false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe ".write" do
|
46
|
+
context "Goodall is enabled" do
|
47
|
+
before(:each) { klass.enable; }
|
48
|
+
it "must deleage to the writer" do
|
49
|
+
expect(mock_writer).to receive(:write).with('string')
|
50
|
+
|
51
|
+
Goodall.write('string')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "Goodall is not enabled" do
|
56
|
+
before(:each) { klass.disable }
|
57
|
+
it "must silently return and not touch the writer" do
|
58
|
+
expect(mock_writer).not_to receive(:write)
|
59
|
+
|
60
|
+
Goodall.write('string')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe ".document_request" do
|
66
|
+
context "Goodall is enabled" do
|
67
|
+
|
68
|
+
context "post with payload" do
|
69
|
+
|
70
|
+
let(:mock_payload) { '{ "foo" : "bar" }' }
|
71
|
+
|
72
|
+
let(:formatted_payload) do
|
73
|
+
"{\n \"foo\" : \"bar\"\n}\n"
|
74
|
+
end
|
75
|
+
|
76
|
+
before(:each) do
|
77
|
+
klass.enable
|
78
|
+
|
79
|
+
mock_handler.stub(
|
80
|
+
:parse_payload
|
81
|
+
).with(
|
82
|
+
mock_payload
|
83
|
+
).and_return(
|
84
|
+
formatted_payload
|
85
|
+
)
|
86
|
+
|
87
|
+
klass.stub(:current_handler).and_return(mock_handler)
|
88
|
+
klass.stub(:writer).and_return(mock_writer)
|
89
|
+
end
|
90
|
+
|
91
|
+
let(:expected_write) do
|
92
|
+
"POST: /foo/bar\n#{formatted_payload}\n"
|
93
|
+
end
|
94
|
+
|
95
|
+
it "must send a formatted response to the writer" do
|
96
|
+
expect(mock_writer).to receive(:write).with(expected_write)
|
97
|
+
|
98
|
+
klass.document_request(:post, '/foo/bar', mock_payload)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context "get without payload" do
|
103
|
+
before(:each) do
|
104
|
+
klass.enable
|
105
|
+
end
|
106
|
+
|
107
|
+
let(:expected_write) do
|
108
|
+
"GET: /foo/bar\n"
|
109
|
+
end
|
110
|
+
|
111
|
+
it "must send a formatted request to the writer" do
|
112
|
+
expect(mock_writer).to receive(:write).with(expected_write)
|
113
|
+
|
114
|
+
klass.document_request(:get, '/foo/bar')
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
context "Goodall is not enabled" do
|
121
|
+
before(:each) { klass.disable }
|
122
|
+
it "must silently return without writing" do
|
123
|
+
mock_writer.should_not_receive(:write)
|
124
|
+
|
125
|
+
klass.document_request(:foo, 'bar', { :baz => :baz })
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe ".document_response" do
|
131
|
+
context "Goodall is enabled" do
|
132
|
+
|
133
|
+
let(:mock_payload) { '{ "foo" : "bar" }' }
|
134
|
+
|
135
|
+
let(:formatted_payload) do
|
136
|
+
"{\n \"foo\" : \"bar\"\n}\n"
|
137
|
+
end
|
138
|
+
|
139
|
+
before(:each) do
|
140
|
+
klass.enable
|
141
|
+
|
142
|
+
mock_handler.stub(
|
143
|
+
:parse_payload
|
144
|
+
).with(
|
145
|
+
mock_payload
|
146
|
+
).and_return(
|
147
|
+
formatted_payload
|
148
|
+
)
|
149
|
+
|
150
|
+
klass.stub(:current_handler).and_return(mock_handler)
|
151
|
+
klass.stub(:writer).and_return(mock_writer)
|
152
|
+
end
|
153
|
+
|
154
|
+
let(:expected_write) do
|
155
|
+
"RESPONSE:\n#{formatted_payload}\n"
|
156
|
+
end
|
157
|
+
|
158
|
+
it "must send a formatted response to the writer" do
|
159
|
+
expect(mock_writer).to receive(:write).with(expected_write)
|
160
|
+
|
161
|
+
klass.document_response(mock_payload)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
context "Goodall is not enabled" do
|
166
|
+
before(:each) { klass.disable }
|
167
|
+
it "must silently return without writing" do
|
168
|
+
mock_writer.should_not_receive(:write)
|
169
|
+
|
170
|
+
klass.document_response({ :baz => :baz })
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe ".register_handler" do
|
176
|
+
it "should add a handler class to the list of registered handlers" do
|
177
|
+
klass.register_handler(:foo_register_test, mock_new_handler)
|
178
|
+
|
179
|
+
expect(klass.registered_handlers).to include([:foo_register_test, mock_new_handler])
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe ".set_handler" do
|
184
|
+
context "handler is registered" do
|
185
|
+
it "should set that handler as the current active handler" do
|
186
|
+
foo_handler_instance = double(:foo_handler_instance)
|
187
|
+
foo_handler_class = double(:foo_handler_class, :new => foo_handler_instance)
|
188
|
+
bar_handler_instance = double(:bar_handler_instance)
|
189
|
+
bar_handler_class = double(:bar_handler_class, :new => bar_handler_instance)
|
190
|
+
|
191
|
+
klass.register_handler(:foo, foo_handler_class)
|
192
|
+
klass.register_handler(:bar, bar_handler_class)
|
193
|
+
|
194
|
+
klass.set_handler(:foo)
|
195
|
+
expect(klass.send(:current_handler)).to eq(foo_handler_instance)
|
196
|
+
|
197
|
+
klass.set_handler(:bar)
|
198
|
+
expect(klass.send(:current_handler)).to eq(bar_handler_instance)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
context "handler is not registered" do
|
202
|
+
it "should raise HandlerNotRegisteredError" do
|
203
|
+
expect{
|
204
|
+
klass.set_handler(:invalid)
|
205
|
+
}.to raise_error(Goodall::HandlerNotRegisteredError)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: goodall
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Matthew Nielsen
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2013-08-30 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: multi_json
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 1
|
31
|
+
- 6
|
32
|
+
version: "1.6"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: bundler
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 9
|
44
|
+
segments:
|
45
|
+
- 1
|
46
|
+
- 3
|
47
|
+
version: "1.3"
|
48
|
+
type: :development
|
49
|
+
version_requirements: *id002
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: rake
|
52
|
+
prerelease: false
|
53
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
type: :development
|
63
|
+
version_requirements: *id003
|
64
|
+
- !ruby/object:Gem::Dependency
|
65
|
+
name: rspec
|
66
|
+
prerelease: false
|
67
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
hash: 23
|
73
|
+
segments:
|
74
|
+
- 2
|
75
|
+
- 10
|
76
|
+
version: "2.10"
|
77
|
+
type: :development
|
78
|
+
version_requirements: *id004
|
79
|
+
description: |-
|
80
|
+
An easy interface for documenting your API while you
|
81
|
+
write your tests.
|
82
|
+
email:
|
83
|
+
- xunker@pyxidis.org
|
84
|
+
executables: []
|
85
|
+
|
86
|
+
extensions: []
|
87
|
+
|
88
|
+
extra_rdoc_files: []
|
89
|
+
|
90
|
+
files:
|
91
|
+
- .gitignore
|
92
|
+
- .rspec
|
93
|
+
- .rvmrc
|
94
|
+
- .travis.yml
|
95
|
+
- Gemfile
|
96
|
+
- LICENSE.txt
|
97
|
+
- README.md
|
98
|
+
- Rakefile
|
99
|
+
- goodall.gemspec
|
100
|
+
- lib/goodall.rb
|
101
|
+
- lib/goodall/command_line_enable.rb
|
102
|
+
- lib/goodall/cucumber.rb
|
103
|
+
- lib/goodall/errors.rb
|
104
|
+
- lib/goodall/handler/base.rb
|
105
|
+
- lib/goodall/handler/json.rb
|
106
|
+
- lib/goodall/handler/xml.rb
|
107
|
+
- lib/goodall/rake_task.rb
|
108
|
+
- lib/goodall/rspec.rb
|
109
|
+
- lib/goodall/version.rb
|
110
|
+
- lib/goodall/writer.rb
|
111
|
+
- lib/tasks/goodall.rake
|
112
|
+
- spec/lib/goodall/handler/json_spec.rb
|
113
|
+
- spec/lib/goodall_spec.rb
|
114
|
+
- spec/spec_helper.rb
|
115
|
+
homepage: http://github.com/xunker/goodall
|
116
|
+
licenses:
|
117
|
+
- MIT
|
118
|
+
post_install_message:
|
119
|
+
rdoc_options: []
|
120
|
+
|
121
|
+
require_paths:
|
122
|
+
- lib
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
124
|
+
none: false
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
hash: 3
|
129
|
+
segments:
|
130
|
+
- 0
|
131
|
+
version: "0"
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
|
+
none: false
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
hash: 3
|
138
|
+
segments:
|
139
|
+
- 0
|
140
|
+
version: "0"
|
141
|
+
requirements: []
|
142
|
+
|
143
|
+
rubyforge_project:
|
144
|
+
rubygems_version: 1.8.25
|
145
|
+
signing_key:
|
146
|
+
specification_version: 3
|
147
|
+
summary: Goodall provides an easy interface for documenting your API while you write your tests. It is compatible with Rspec, Cucumber and test-unit, as well as others. Goodall is named after Jane Goodall who has spent her life observing and documenting the behviour of chimpanzees.
|
148
|
+
test_files:
|
149
|
+
- spec/lib/goodall/handler/json_spec.rb
|
150
|
+
- spec/lib/goodall_spec.rb
|
151
|
+
- spec/spec_helper.rb
|