blueprint_agreement 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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.ruby-version +1 -0
- data/.travis.yml +9 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/LICENSE.txt +22 -0
- data/README.md +89 -0
- data/Rakefile +5 -0
- data/blueprint_agreement.gemspec +26 -0
- data/lib/blueprint_agreement/api_services/drakov_service.rb +43 -0
- data/lib/blueprint_agreement/config.rb +38 -0
- data/lib/blueprint_agreement/errors.rb +19 -0
- data/lib/blueprint_agreement/minitest/assertions.rb +20 -0
- data/lib/blueprint_agreement/minitest/expectations.rb +3 -0
- data/lib/blueprint_agreement/request_builder.rb +89 -0
- data/lib/blueprint_agreement/server.rb +31 -0
- data/lib/blueprint_agreement/utils/request_logger.rb +33 -0
- data/lib/blueprint_agreement/utils/requester.rb +72 -0
- data/lib/blueprint_agreement/utils/response_parser.rb +11 -0
- data/lib/blueprint_agreement/version.rb +3 -0
- data/lib/blueprint_agreement.rb +52 -0
- data/tasks/test.rake +7 -0
- data/test/fixtures/hello_api.md +31 -0
- data/test/integration/simple_api_test.rb +40 -0
- data/test/request_builder_test.rb +33 -0
- data/test/server_test.rb +78 -0
- data/test/support/rails_mocks/request.rb +18 -0
- data/test/support/rails_mocks/response.rb +10 -0
- data/test/test_helper.rb +22 -0
- metadata +150 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: b05de0aada665a43509a4e8fe98ccc07b5af9033
|
|
4
|
+
data.tar.gz: 2da0ab1502163df9f913a44e04ac8aa11e17093a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d5bcb4201a35022d0ebd95877a0e8066d293e162c40ff8882dee4d09a4926ab49248b8d99cd1938991a3a91cc1dd89bb1258930382aaffc34e0a1acb279b234d
|
|
7
|
+
data.tar.gz: 41ac19df8f3ddb8249c27ad9d073e9f53db68423aafcca0f877b04afe8353230e3bcae58df2d84c1c9af9d8664196b32b98c4aed59b57f91bb7fd5f148c21c58
|
data/.gitignore
ADDED
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.3.0
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2010-2016 Google, Inc. http://angularjs.org
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2016 Charly Palencia
|
|
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,89 @@
|
|
|
1
|
+
# Blueprint Agreement
|
|
2
|
+
|
|
3
|
+
A Minitest API Documentation Matcher based on ApiBluePrint schema.
|
|
4
|
+
|
|
5
|
+
Note: This Gem Is Currently on Development.
|
|
6
|
+
|
|
7
|
+
## Description
|
|
8
|
+
|
|
9
|
+
- A ruby library for Validate API Blueprint Documentation
|
|
10
|
+
- Support MiniTest Assertion and Spec format
|
|
11
|
+
- Use drakov node library to serve Mock API server
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## Getting Started
|
|
15
|
+
|
|
16
|
+
Add this line to your application's Gemfile:
|
|
17
|
+
|
|
18
|
+
gem 'blueprint-agreement'
|
|
19
|
+
|
|
20
|
+
And then execute:
|
|
21
|
+
|
|
22
|
+
$ bundle
|
|
23
|
+
|
|
24
|
+
Or install it yourself as:
|
|
25
|
+
|
|
26
|
+
$ gem install agreement
|
|
27
|
+
|
|
28
|
+
*MiniTest*
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
require 'blueprint_agreement'
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
### Quick Start
|
|
37
|
+
|
|
38
|
+
Blueprint agreement works based on a markdown file with an valid API Blueprint format. Add your file into `/docs` folder in your project root folder (or set your custom documentation folder)
|
|
39
|
+
|
|
40
|
+
./docs/test.md
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
FORMAT: 1A
|
|
44
|
+
|
|
45
|
+
# The Simplest API
|
|
46
|
+
|
|
47
|
+
## API Blueprint
|
|
48
|
+
|
|
49
|
+
# GET /message
|
|
50
|
+
+ Request 200 (application/json)
|
|
51
|
+
+ Response 200 (application/json)
|
|
52
|
+
|
|
53
|
+
+ Body
|
|
54
|
+
|
|
55
|
+
{
|
|
56
|
+
'name': 'Hello World'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Then, test your documentation:
|
|
62
|
+
|
|
63
|
+
``` ruby
|
|
64
|
+
|
|
65
|
+
describe Test do
|
|
66
|
+
it 'has a valid response' do
|
|
67
|
+
get :index
|
|
68
|
+
response.shall_agree_with('test.md')
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Config File
|
|
74
|
+
|
|
75
|
+
/config/initializer/blueprint_agreement.rb
|
|
76
|
+
|
|
77
|
+
``` ruby
|
|
78
|
+
BlueprintAgreement::Config.port = '8081' #Default port for Drakov Server
|
|
79
|
+
BlueprintAgreement::Config.server_path= './docs' #Default server path for Drakov Server
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
## Contributing
|
|
84
|
+
|
|
85
|
+
1. Fork it ( http://github.com/charly-palencia/agreement/fork )
|
|
86
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
87
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
88
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
89
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'blueprint_agreement/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "blueprint_agreement".freeze
|
|
8
|
+
spec.version = BlueprintAgreement::VERSION
|
|
9
|
+
spec.authors = ["Charly Palencia"]
|
|
10
|
+
spec.email = ["charly.palencia@koombea.com"]
|
|
11
|
+
spec.summary = %q{A Minitest API Documentation Matcher , based on ApiBluePrint schema.}
|
|
12
|
+
spec.description = %q{A Minitest API Documentation Matcher , based on ApiBluePrint schema.}
|
|
13
|
+
spec.homepage = "http://www.chalien.com"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
|
|
16
|
+
spec.files = `git ls-files`.split($/)
|
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
19
|
+
spec.require_paths = ["lib"]
|
|
20
|
+
|
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
|
22
|
+
spec.add_development_dependency "rake", "~>11.1"
|
|
23
|
+
spec.add_development_dependency 'minitest', "~>5.9"
|
|
24
|
+
spec.add_development_dependency 'mocha', "~>1.1"
|
|
25
|
+
spec.add_development_dependency 'byebug', "~>9.0"
|
|
26
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module BlueprintAgreement
|
|
2
|
+
class DrakovService
|
|
3
|
+
attr :pid, :port, :hostname, :root_path
|
|
4
|
+
|
|
5
|
+
def initialize(hostname = 'http://localhost')
|
|
6
|
+
@port = Config.port
|
|
7
|
+
@hostname = hostname
|
|
8
|
+
@root_path = Config.server_path
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def start(path)
|
|
12
|
+
@pid = spawn "drakov -f #{root_path}/#{path} -p #{port} --header Authorization", options
|
|
13
|
+
Config.active_service = { pid: @pid, path: path }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def stop
|
|
17
|
+
Process.kill 'TERM', Config.active_service[:pid]
|
|
18
|
+
Config.active_service = nil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def host
|
|
22
|
+
"http://localhost:#{port}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def installed?
|
|
26
|
+
`which drakov`.length > 0
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def install
|
|
30
|
+
print "installing drakov.."
|
|
31
|
+
pid = Process.spawn "sudo npm install -g drakov"
|
|
32
|
+
Process.wait pid
|
|
33
|
+
print "Drakov installed 🍺 "
|
|
34
|
+
end
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def options
|
|
38
|
+
return {} if ENV["AGREEMENT_LOUD"]
|
|
39
|
+
|
|
40
|
+
{ out: '/dev/null' }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module BlueprintAgreement
|
|
2
|
+
module Config
|
|
3
|
+
extend self
|
|
4
|
+
@@active_service = nil
|
|
5
|
+
|
|
6
|
+
def configure; yield self end
|
|
7
|
+
|
|
8
|
+
def port=(port)
|
|
9
|
+
@port = port
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def active_service?
|
|
13
|
+
!!@@active_service
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def active_service=(active_service)
|
|
17
|
+
@@active_service = active_service
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def active_service
|
|
21
|
+
@@active_service
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def server_path(path = './docs')
|
|
25
|
+
@server_path ||= path
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def default_format
|
|
29
|
+
'*.apib'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def port
|
|
33
|
+
#default port so 8081
|
|
34
|
+
@port || '8081'
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module BlueprintAgreement
|
|
2
|
+
class EndpointNotFound < StandardError;
|
|
3
|
+
attr :request, :response
|
|
4
|
+
|
|
5
|
+
def initialize(request)
|
|
6
|
+
@request = request
|
|
7
|
+
@response = request
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def message
|
|
11
|
+
%{
|
|
12
|
+
Response:
|
|
13
|
+
uri: #{response.uri}
|
|
14
|
+
code: #{response.code}
|
|
15
|
+
body: #{response.msg}
|
|
16
|
+
}
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Minitest
|
|
2
|
+
module Assertions
|
|
3
|
+
def assert_shall_agree_upon contract_name, response
|
|
4
|
+
result = BlueprintAgreement::Utils.response_parser(response.body)
|
|
5
|
+
api_service = BlueprintAgreement::DrakovService.new
|
|
6
|
+
server = BlueprintAgreement::Server.new(
|
|
7
|
+
api_service: api_service,
|
|
8
|
+
config: BlueprintAgreement::Config
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
begin
|
|
12
|
+
server.start(contract_name)
|
|
13
|
+
request = BlueprintAgreement::RequestBuilder.for(self)
|
|
14
|
+
requester = BlueprintAgreement::Utils::Requester.new(request, server)
|
|
15
|
+
expected = BlueprintAgreement::Utils.response_parser(requester.perform.body)
|
|
16
|
+
assert_equal expected, result
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module BlueprintAgreement
|
|
2
|
+
class RequestBuilder
|
|
3
|
+
|
|
4
|
+
def self.for(context)
|
|
5
|
+
klass = case
|
|
6
|
+
when rails?
|
|
7
|
+
RailsRequest
|
|
8
|
+
when rack_test?
|
|
9
|
+
RackTestRequest
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
klass.new(context)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.rails?
|
|
16
|
+
!!defined?(Rails)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.rack_test?
|
|
20
|
+
!!defined?(Rack::Test)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class RackTestRequest
|
|
24
|
+
|
|
25
|
+
def initialize(context)
|
|
26
|
+
@context = context
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def body
|
|
30
|
+
@body ||= request.body.read
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def content_type
|
|
34
|
+
request.content_type
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def request_method
|
|
38
|
+
request.request_method
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def fullpath
|
|
42
|
+
request.fullpath
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def headers
|
|
46
|
+
@context.rack_test_session.instance_variable_get(:@headers)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def request
|
|
50
|
+
@context.last_request
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class RailsRequest
|
|
55
|
+
HEADER_PATCH = {"CONTENT_TYPE" => "Content-Type", "HTTP_AUTHORIZATION" => "Authorization"}
|
|
56
|
+
|
|
57
|
+
def initialize(context)
|
|
58
|
+
@context = context
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def body
|
|
62
|
+
@body ||= request.body.read
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def content_type
|
|
66
|
+
request.content_type
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def request_method
|
|
70
|
+
request.request_method
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def fullpath
|
|
74
|
+
request.fullpath
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def headers
|
|
78
|
+
HEADER_PATCH.each_with_object({}) do |header, result|
|
|
79
|
+
header_name, key = header
|
|
80
|
+
result[key] = @context.request.headers[header_name]
|
|
81
|
+
end.compact
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def request
|
|
85
|
+
@context.request
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module BlueprintAgreement
|
|
2
|
+
class Server
|
|
3
|
+
|
|
4
|
+
def initialize(api_service:, config:)
|
|
5
|
+
@api_service = api_service
|
|
6
|
+
@config = config
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def start(path=@config.default_format)
|
|
10
|
+
@api_service.install unless @api_service.installed?
|
|
11
|
+
|
|
12
|
+
if @config.active_service
|
|
13
|
+
restart(path) if @config.active_service[:path] != path
|
|
14
|
+
else
|
|
15
|
+
@api_service.start(path)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def restart(path)
|
|
20
|
+
stop && @api_service.start(path)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def stop
|
|
24
|
+
@api_service.stop
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def host
|
|
28
|
+
@api_service.host
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'singleton'
|
|
2
|
+
|
|
3
|
+
module BlueprintAgreement
|
|
4
|
+
module Utils
|
|
5
|
+
class RequestLogger
|
|
6
|
+
include Singleton
|
|
7
|
+
|
|
8
|
+
def for(body:, headers:, path:, request_method:)
|
|
9
|
+
@body = body
|
|
10
|
+
@path = path
|
|
11
|
+
@request_method = request_method
|
|
12
|
+
@headers = headers
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def print
|
|
16
|
+
header_output = @headers.to_a.map { |header| header.join("=") }.join("\n")
|
|
17
|
+
%{
|
|
18
|
+
Method: #{@request_method}
|
|
19
|
+
Path: #{@path}
|
|
20
|
+
|
|
21
|
+
Details
|
|
22
|
+
|
|
23
|
+
Headers:
|
|
24
|
+
|
|
25
|
+
#{ header_output }
|
|
26
|
+
|
|
27
|
+
Body:
|
|
28
|
+
#{@body}
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require 'net/http'
|
|
2
|
+
|
|
3
|
+
module BlueprintAgreement
|
|
4
|
+
module Utils
|
|
5
|
+
class Requester
|
|
6
|
+
REQUEST_OPTIONS = {
|
|
7
|
+
"GET" => Net::HTTP::Get,
|
|
8
|
+
"POST" => Net::HTTP::Post,
|
|
9
|
+
"PUT" => Net::HTTP::Put,
|
|
10
|
+
"DELETE" => Net::HTTP::Delete
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
def initialize(request, server)
|
|
14
|
+
@current_request = request
|
|
15
|
+
@server = server
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def perform
|
|
19
|
+
begin
|
|
20
|
+
Net::HTTP.start(request_path.host, request_path.port) do |http|
|
|
21
|
+
request = REQUEST_OPTIONS[request_method].new request_path
|
|
22
|
+
set_form_data(request)
|
|
23
|
+
set_headers(request)
|
|
24
|
+
|
|
25
|
+
request_logger.for({
|
|
26
|
+
body: current_request.body,
|
|
27
|
+
headers: current_request.headers,
|
|
28
|
+
path: request_path,
|
|
29
|
+
request_method: request_method
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
http.request(request).tap do |http_request|
|
|
33
|
+
puts request_logger.print if ENV["AGREEMENT_LOUD"]
|
|
34
|
+
raise EndpointNotFound.new(http_request) if http_request.code == "404"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
rescue Errno::ECONNREFUSED
|
|
38
|
+
sleep 1
|
|
39
|
+
perform
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def request_logger
|
|
46
|
+
Utils::RequestLogger.instance
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def set_headers(request)
|
|
50
|
+
current_request.headers.each do |key, value|
|
|
51
|
+
request[key] = value
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def set_form_data(request)
|
|
56
|
+
if ['POST', 'PUT'].include? request_method
|
|
57
|
+
request.body = current_request.body
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def request_method
|
|
62
|
+
@request_method ||= current_request.request_method
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def request_path
|
|
66
|
+
@request_path ||= URI.join(server.host, current_request.fullpath)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
attr_reader :current_request, :server
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# gem "minitest"
|
|
2
|
+
require "minitest"
|
|
3
|
+
require "minitest"
|
|
4
|
+
require "minitest/spec"
|
|
5
|
+
require "minitest/mock"
|
|
6
|
+
require "blueprint_agreement/version"
|
|
7
|
+
require 'blueprint_agreement/config'
|
|
8
|
+
require 'blueprint_agreement/errors'
|
|
9
|
+
require 'blueprint_agreement/api_services/drakov_service'
|
|
10
|
+
require "blueprint_agreement/server"
|
|
11
|
+
require 'blueprint_agreement/request_builder'
|
|
12
|
+
require 'blueprint_agreement/utils/request_logger'
|
|
13
|
+
require 'blueprint_agreement/utils/requester'
|
|
14
|
+
require 'blueprint_agreement/utils/response_parser'
|
|
15
|
+
require 'blueprint_agreement/minitest/assertions'
|
|
16
|
+
require 'blueprint_agreement/minitest/expectations'
|
|
17
|
+
|
|
18
|
+
# ========================== BluePrintAgreement ==================================
|
|
19
|
+
# +-----------+ +-------------------+ +-----------------+
|
|
20
|
+
# | Minitest | | BlueprintAgreement| |Node Environment |
|
|
21
|
+
# +----+------+ +---------+---------+ +-----------------+
|
|
22
|
+
# | | |
|
|
23
|
+
# | shall_agree_with | |
|
|
24
|
+
# +------------------------> | /documentation_endpoint |
|
|
25
|
+
# | +------------------------------> |
|
|
26
|
+
# | | |
|
|
27
|
+
# | | |
|
|
28
|
+
# | | |
|
|
29
|
+
# | | <response> |
|
|
30
|
+
# | | <----------------------------+ |
|
|
31
|
+
# | | |
|
|
32
|
+
# | assert_equal | |
|
|
33
|
+
# | <------------------------+ |
|
|
34
|
+
# | | |
|
|
35
|
+
# | | |
|
|
36
|
+
# | | |
|
|
37
|
+
# | | |
|
|
38
|
+
# | | |
|
|
39
|
+
# | | |
|
|
40
|
+
# +-+-+ +--+--+ +-+-+
|
|
41
|
+
#
|
|
42
|
+
module BlueprintAgreement
|
|
43
|
+
Config.configure do |config|
|
|
44
|
+
config.port = '8082'
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
Minitest.after_run do
|
|
49
|
+
if BlueprintAgreement::Config.active_service?
|
|
50
|
+
Process.kill 'TERM', BlueprintAgreement::Config.active_service[:pid]
|
|
51
|
+
end
|
|
52
|
+
end
|
data/tasks/test.rake
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
FORMAT: 1A
|
|
2
|
+
|
|
3
|
+
# The Simplest API
|
|
4
|
+
This is one of the simplest APIs written in the **API Blueprint**. One plain
|
|
5
|
+
resource combined with a method and that's it! We will explain what is going on
|
|
6
|
+
in the next installment -
|
|
7
|
+
[Resource and Actions](02.%20Resource%20and%20Actions.md).
|
|
8
|
+
|
|
9
|
+
**Note:** As we progress through the examples, do not also forget to view the
|
|
10
|
+
[Raw](https://raw.github.com/apiaryio/api-blueprint/master/examples/01.%20Simplest%20API.md)
|
|
11
|
+
code to see what is really going on in the API Blueprint, as opposed to just
|
|
12
|
+
seeing the output of the Github Markdown parser.
|
|
13
|
+
|
|
14
|
+
Also please keep in mind that every single example in this course is a **real
|
|
15
|
+
API Blueprint** and as such you can **parse** it with the
|
|
16
|
+
[API Blueprint parser](https://github.com/apiaryio/drafter) or one of its
|
|
17
|
+
[bindings](https://github.com/apiaryio/drafter#bindings).
|
|
18
|
+
|
|
19
|
+
## API Blueprint
|
|
20
|
+
+ [This: Raw API Blueprint](https://raw.github.com/apiaryio/api-blueprint/master/examples/01.%20Simplest%20API.md)
|
|
21
|
+
+ [Next: Resource and Actions](02.%20Resource%20and%20Actions.md)
|
|
22
|
+
|
|
23
|
+
# GET /message
|
|
24
|
+
+ Request 200 (application/json)
|
|
25
|
+
+ Response 200 (application/json)
|
|
26
|
+
|
|
27
|
+
+ Body
|
|
28
|
+
|
|
29
|
+
{
|
|
30
|
+
'name': 'Hello World'
|
|
31
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
require 'blueprint_agreement'
|
|
3
|
+
|
|
4
|
+
class RackTestSession
|
|
5
|
+
def initialize
|
|
6
|
+
@headers = { "Content-Type" => "application/json" }
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
describe "Rack Test" do
|
|
11
|
+
let(:body) { "\n{\n 'name': 'Hello World'\n}\n" }
|
|
12
|
+
let(:last_request) { RailsMocks::Request.new(fullpath: endpoint) }
|
|
13
|
+
let(:last_response) { RailsMocks::Response.new(body: body, request: last_request) }
|
|
14
|
+
let(:rack_test_session) { RackTestSession.new }
|
|
15
|
+
|
|
16
|
+
before do
|
|
17
|
+
module Rack; module Test; end; end
|
|
18
|
+
BlueprintAgreement::Config.server_path('./test/fixtures')
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe 'when blueprint agreement was included but never used' do
|
|
22
|
+
it 'returns a valid api request' do
|
|
23
|
+
expect(true).must_equal(true)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe 'with valid request' do
|
|
28
|
+
let(:endpoint){ '/message' }
|
|
29
|
+
it 'returns a valid api request' do
|
|
30
|
+
last_response.shall_agree_upon('hello_api.md')
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe 'with invalid request' do
|
|
35
|
+
let(:endpoint){ '/not_valid' }
|
|
36
|
+
it 'returns a Not Found Route error' do
|
|
37
|
+
expect { last_response.shall_agree_upon('hello_api.md') }.must_raise(BlueprintAgreement::EndpointNotFound)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
require './lib/blueprint_agreement/request_builder'
|
|
3
|
+
|
|
4
|
+
describe BlueprintAgreement::RequestBuilder do
|
|
5
|
+
let(:described_class) { BlueprintAgreement::RequestBuilder }
|
|
6
|
+
|
|
7
|
+
describe '.for' do
|
|
8
|
+
let(:context) { mock() }
|
|
9
|
+
let(:result) { described_class.for(context) }
|
|
10
|
+
|
|
11
|
+
describe 'when rails is defined' do
|
|
12
|
+
before do
|
|
13
|
+
described_class.stubs(:rails?).returns(true)
|
|
14
|
+
described_class.stubs(:rack_test?).returns(false)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'returns rails request instance' do
|
|
18
|
+
expect(described_class.for(context)).must_be_kind_of(described_class::RailsRequest)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe 'when rack-test is defined' do
|
|
23
|
+
before do
|
|
24
|
+
described_class.stubs(:rails?).returns(false)
|
|
25
|
+
described_class.stubs(:rack_test?).returns(true)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'returns a rack test request instance' do
|
|
29
|
+
expect(described_class.for(context)).must_be_kind_of(described_class::RackTestRequest)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
data/test/server_test.rb
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
require './lib/blueprint_agreement/server'
|
|
3
|
+
|
|
4
|
+
describe BlueprintAgreement::Server do
|
|
5
|
+
let(:api_service) { ApiService.new(config) }
|
|
6
|
+
let(:described_class) { BlueprintAgreement::Server }
|
|
7
|
+
let(:instance) { described_class.new(api_service: api_service, config: config) }
|
|
8
|
+
let(:config) { mock() }
|
|
9
|
+
|
|
10
|
+
describe '#start' do
|
|
11
|
+
let(:active_service) { { path: '*.apib' } }
|
|
12
|
+
let(:config) { mock(default_format: '*.apib') }
|
|
13
|
+
|
|
14
|
+
describe 'when api service is not installed' do
|
|
15
|
+
let(:active_service){ nil }
|
|
16
|
+
before do
|
|
17
|
+
config.unstub(:active_service)
|
|
18
|
+
config.stubs(:active_service).returns(active_service).once
|
|
19
|
+
api_service.stubs(:installed?).returns(false).once
|
|
20
|
+
api_service.stubs(:install).returns(true).once
|
|
21
|
+
api_service.stubs(:start).returns(true).once
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'should install api service' do
|
|
25
|
+
expect(instance.start).must_equal true
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe 'when api service is already installed' do
|
|
30
|
+
let(:config) { mock(default_format: '*.apib') }
|
|
31
|
+
|
|
32
|
+
before do
|
|
33
|
+
api_service.stubs(:installed?).returns(true).once
|
|
34
|
+
api_service.stubs(:install).never
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe 'and an active service exists' do
|
|
38
|
+
it 'does not stop the service when it is the same path' do
|
|
39
|
+
config.unstub(:active_service)
|
|
40
|
+
config.stubs(:active_service).returns(active_service).twice
|
|
41
|
+
api_service.stubs(:stop).returns(true).never
|
|
42
|
+
api_service.stubs(:start).returns(true).never
|
|
43
|
+
instance.start
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'restarts the service' do
|
|
47
|
+
config.unstub(:default_format)
|
|
48
|
+
config.unstub(:active_service)
|
|
49
|
+
config.stubs(:active_service).returns(active_service).twice
|
|
50
|
+
config.stubs(:default_format).never
|
|
51
|
+
api_service.stubs(:stop).returns(true).once
|
|
52
|
+
api_service.stubs(:start).returns(true).once
|
|
53
|
+
instance.start('differnt.apib')
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
describe 'without any active service' do
|
|
58
|
+
it 'stops the service and start again with a new active_service' do
|
|
59
|
+
config.stubs(:active_service).returns(nil)
|
|
60
|
+
api_service.stubs(:start).returns(true).once
|
|
61
|
+
instance.start
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe '#stop' do
|
|
69
|
+
before do
|
|
70
|
+
api_service.stubs(:stop).returns(true).once
|
|
71
|
+
config.stubs(:active_service)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it 'should the api service' do
|
|
75
|
+
expect(instance.stop).must_equal true
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module RailsMocks
|
|
2
|
+
class Request
|
|
3
|
+
attr_reader :fullpath, :request_method, :content_type, :authorization, :body
|
|
4
|
+
|
|
5
|
+
def initialize(response: '', fullpath: '/message', request_method: 'GET', authorization: '', content_type: 'application/json', body: '')
|
|
6
|
+
@response = response
|
|
7
|
+
@fullpath = URI(fullpath)
|
|
8
|
+
@request_method = request_method
|
|
9
|
+
@authorization = authorization
|
|
10
|
+
@body = StringIO.new(body)
|
|
11
|
+
@content_type = content_type
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def has_content_type?
|
|
15
|
+
!content_type.nil?
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/test/test_helper.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
|
2
|
+
require 'bundler/setup'
|
|
3
|
+
Bundler.setup
|
|
4
|
+
require 'minitest/autorun'
|
|
5
|
+
require 'minitest/unit'
|
|
6
|
+
require 'mocha/mini_test'
|
|
7
|
+
|
|
8
|
+
ROOT_PATH = File.dirname(__FILE__)
|
|
9
|
+
Dir[File.join(ROOT_PATH, "support/**/*.rb")].each { |f| require f }
|
|
10
|
+
|
|
11
|
+
class ApiService
|
|
12
|
+
|
|
13
|
+
def initialize(config)
|
|
14
|
+
@config = config
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def start; end
|
|
18
|
+
def installed?; end
|
|
19
|
+
def install; end
|
|
20
|
+
def stop; end
|
|
21
|
+
def options; end
|
|
22
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: blueprint_agreement
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Charly Palencia
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2016-09-30 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.5'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.5'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '11.1'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '11.1'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: minitest
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '5.9'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '5.9'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: mocha
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '1.1'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '1.1'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: byebug
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '9.0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '9.0'
|
|
83
|
+
description: A Minitest API Documentation Matcher , based on ApiBluePrint schema.
|
|
84
|
+
email:
|
|
85
|
+
- charly.palencia@koombea.com
|
|
86
|
+
executables: []
|
|
87
|
+
extensions: []
|
|
88
|
+
extra_rdoc_files: []
|
|
89
|
+
files:
|
|
90
|
+
- ".gitignore"
|
|
91
|
+
- ".ruby-version"
|
|
92
|
+
- ".travis.yml"
|
|
93
|
+
- Gemfile
|
|
94
|
+
- LICENSE
|
|
95
|
+
- LICENSE.txt
|
|
96
|
+
- README.md
|
|
97
|
+
- Rakefile
|
|
98
|
+
- blueprint_agreement.gemspec
|
|
99
|
+
- lib/blueprint_agreement.rb
|
|
100
|
+
- lib/blueprint_agreement/api_services/drakov_service.rb
|
|
101
|
+
- lib/blueprint_agreement/config.rb
|
|
102
|
+
- lib/blueprint_agreement/errors.rb
|
|
103
|
+
- lib/blueprint_agreement/minitest/assertions.rb
|
|
104
|
+
- lib/blueprint_agreement/minitest/expectations.rb
|
|
105
|
+
- lib/blueprint_agreement/request_builder.rb
|
|
106
|
+
- lib/blueprint_agreement/server.rb
|
|
107
|
+
- lib/blueprint_agreement/utils/request_logger.rb
|
|
108
|
+
- lib/blueprint_agreement/utils/requester.rb
|
|
109
|
+
- lib/blueprint_agreement/utils/response_parser.rb
|
|
110
|
+
- lib/blueprint_agreement/version.rb
|
|
111
|
+
- tasks/test.rake
|
|
112
|
+
- test/fixtures/hello_api.md
|
|
113
|
+
- test/integration/simple_api_test.rb
|
|
114
|
+
- test/request_builder_test.rb
|
|
115
|
+
- test/server_test.rb
|
|
116
|
+
- test/support/rails_mocks/request.rb
|
|
117
|
+
- test/support/rails_mocks/response.rb
|
|
118
|
+
- test/test_helper.rb
|
|
119
|
+
homepage: http://www.chalien.com
|
|
120
|
+
licenses:
|
|
121
|
+
- MIT
|
|
122
|
+
metadata: {}
|
|
123
|
+
post_install_message:
|
|
124
|
+
rdoc_options: []
|
|
125
|
+
require_paths:
|
|
126
|
+
- lib
|
|
127
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - ">="
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '0'
|
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
133
|
+
requirements:
|
|
134
|
+
- - ">="
|
|
135
|
+
- !ruby/object:Gem::Version
|
|
136
|
+
version: '0'
|
|
137
|
+
requirements: []
|
|
138
|
+
rubyforge_project:
|
|
139
|
+
rubygems_version: 2.5.1
|
|
140
|
+
signing_key:
|
|
141
|
+
specification_version: 4
|
|
142
|
+
summary: A Minitest API Documentation Matcher , based on ApiBluePrint schema.
|
|
143
|
+
test_files:
|
|
144
|
+
- test/fixtures/hello_api.md
|
|
145
|
+
- test/integration/simple_api_test.rb
|
|
146
|
+
- test/request_builder_test.rb
|
|
147
|
+
- test/server_test.rb
|
|
148
|
+
- test/support/rails_mocks/request.rb
|
|
149
|
+
- test/support/rails_mocks/response.rb
|
|
150
|
+
- test/test_helper.rb
|