skink 0.1.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.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ tags
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in skink.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Todd Thomas
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,5 @@
1
+ # Skink
2
+
3
+ Skink is Capybara's smaller, more primitive companion. Button-clicking
4
+ and JavaScript are beyond Skink's intellectual abilities, but Skink is
5
+ happy to help test your REST API.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,87 @@
1
+ require 'skink'
2
+
3
+ module Skink
4
+ module Client
5
+
6
+ class Base
7
+ def with_header(name, value)
8
+ raise NotImplementedError
9
+ end
10
+
11
+ def with_basic_auth(user_name, email_address)
12
+ raise NotImplementedError
13
+ end
14
+
15
+ def head(path)
16
+ raise NotImplementedError
17
+ end
18
+
19
+ def get(path)
20
+ raise NotImplementedError
21
+ end
22
+
23
+ def post(path, body = nil)
24
+ raise NotImplementedError
25
+ end
26
+
27
+ def put(path, body)
28
+ raise NotImplementedError
29
+ end
30
+
31
+ def delete(path)
32
+ raise NotImplementedError
33
+ end
34
+
35
+ def trace(path)
36
+ raise NotImplementedError
37
+ end
38
+
39
+ def options(path)
40
+ raise NotImplementedError
41
+ end
42
+
43
+ def connect(path)
44
+ raise NotImplementedError
45
+ end
46
+
47
+ def patch(path, body = nil)
48
+ raise NotImplementedError
49
+ end
50
+
51
+ def response
52
+ raise NotImplementedError
53
+ end
54
+
55
+ def method_missing(name, *args)
56
+ if name.match(/with_(\w+)_header/)
57
+ with_header(normalize_header_name($1), *args)
58
+ else
59
+ super
60
+ end
61
+ end
62
+
63
+ private
64
+ def normalize_header_name(name)
65
+ # c.f http://en.wikipedia.org/wiki/List_of_HTTP_header_fields
66
+
67
+ case name
68
+ # special cases
69
+ when /^content_md5$/i
70
+ return "Content-MD5"
71
+ when /^te$/i
72
+ return "TE"
73
+ when /^dnt$/i
74
+ return "DNT"
75
+ when /^etag$/i
76
+ return "ETag"
77
+ when /^www_authenticate$/i
78
+ return "WWW-Authenticate"
79
+ end
80
+
81
+ # otherwise this should work
82
+ name.split("_").map(&:capitalize).join("-")
83
+ end
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,15 @@
1
+ module Skink
2
+ module Client
3
+
4
+ class Response
5
+ def status_code
6
+ raise NotImplementedError
7
+ end
8
+
9
+ def body
10
+ raise NotImplementedError
11
+ end
12
+ end
13
+
14
+ end
15
+ end
data/lib/skink/dsl.rb ADDED
@@ -0,0 +1,50 @@
1
+ require 'skink'
2
+ require 'skink/client/base'
3
+ require 'skink/rack_test_client/rack_test_client'
4
+ require 'skink/resourceful_client/resourceful_client'
5
+
6
+ module Skink
7
+ class << self
8
+ attr_writer :rack_app
9
+ attr_accessor :base_url
10
+
11
+ def rack_app
12
+ raise ConfigurationError.new "You must set Skink.rack_app to use the DSL." unless @rack_app
13
+ @rack_app
14
+ end
15
+
16
+ def client
17
+ @client ||= new_client
18
+ end
19
+
20
+ def new_client
21
+ if base_url.nil? || base_url.empty?
22
+ RackTestClient.new(rack_app)
23
+ else
24
+ ResourcefulClient.new(base_url)
25
+ end
26
+ end
27
+
28
+ def reset_client!
29
+ @client = nil
30
+ end
31
+ end
32
+
33
+ module DSL
34
+ def self.included(base)
35
+ puts "Skink::DSL being included in #{base.inspect}"
36
+ end
37
+
38
+ def self.extended(base)
39
+ puts "Skink::DSL extending #{base.inspect}"
40
+ end
41
+
42
+ Skink::Client::Base.instance_methods(false).each do |method|
43
+ define_method method do |*args, &block|
44
+ Skink.client.send method, *args, &block
45
+ end
46
+ end
47
+ end
48
+
49
+ extend Skink::DSL
50
+ end
@@ -0,0 +1,12 @@
1
+ require 'skink/dsl'
2
+
3
+ Skink.rack_app = Rack::Builder.new do
4
+ map "/" do
5
+ if Rails.version.to_f >= 3.0
6
+ run Rails.application
7
+ else # Rails 2
8
+ use Rails::Rack::Static
9
+ run ActionController::Dispatcher.new
10
+ end
11
+ end
12
+ end.to_app
@@ -0,0 +1,11 @@
1
+ require 'skink/dsl'
2
+
3
+ RSpec.configure do |config|
4
+ config.include Skink::DSL
5
+
6
+ config.after do
7
+ if self.class.include?(Skink::DSL)
8
+ Skink.reset_client!
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,55 @@
1
+ require 'skink/client/base'
2
+ require 'skink/rack_test_client/rack_test_response'
3
+
4
+ require 'rack/test'
5
+
6
+ module Skink
7
+
8
+ class RackTestClient < Client::Base
9
+ include Rack::Test::Methods
10
+
11
+ attr_reader :app, :last_response
12
+
13
+ def initialize(rack_app)
14
+ @app = rack_app
15
+ end
16
+
17
+ def with_basic_auth(user_name, password)
18
+ authorize(user_name, password)
19
+ end
20
+
21
+ def with_header(name, value)
22
+ header(name, value)
23
+ end
24
+
25
+ def head(path)
26
+ resp = super path
27
+ @last_response = RackTestResponse.new resp
28
+ end
29
+
30
+ def get(path)
31
+ resp = super path
32
+ @last_response = RackTestResponse.new resp
33
+ end
34
+
35
+ def post(path, body = nil)
36
+ resp = super(path, body)
37
+ @last_response = RackTestResponse.new resp
38
+ end
39
+
40
+ def put(path, body)
41
+ resp = super(path, body)
42
+ @last_response = RackTestResponse.new resp
43
+ end
44
+
45
+ def delete(path)
46
+ resp = super path
47
+ @last_response = RackTestResponse.new resp
48
+ end
49
+
50
+ def response
51
+ last_response
52
+ end
53
+ end
54
+
55
+ end
@@ -0,0 +1,21 @@
1
+ require 'skink/client/response'
2
+
3
+ module Skink
4
+
5
+ class RackTestResponse < Client::Response
6
+ attr_reader :native_response
7
+
8
+ def initialize(native_response)
9
+ @native_response = native_response
10
+ end
11
+
12
+ def status_code
13
+ native_response.status
14
+ end
15
+
16
+ def body
17
+ native_response.body
18
+ end
19
+ end
20
+
21
+ end
@@ -0,0 +1,85 @@
1
+ require 'skink/client/base'
2
+ require 'skink/resourceful_client/resourceful_response'
3
+
4
+ require 'resourceful'
5
+ require 'httpauth'
6
+
7
+ module Skink
8
+
9
+ class ResourcefulClient < Client::Base
10
+ attr_reader :base_url, :headers, :http_accessor, :last_response
11
+
12
+ def initialize(base_url)
13
+ @base_url = base_url
14
+ @headers = {}
15
+ end
16
+
17
+ def http_accessor
18
+ @http_accessor ||= Resourceful::HttpAccessor.new
19
+ end
20
+
21
+ def with_basic_auth(user_name, password)
22
+ #http_accessor.add_authenticator Resourceful::PromiscuousBasicAuthenticator.new(user_name, password)
23
+ http_accessor.add_authenticator Resourceful::BasicAuthenticator.new("Restricted Area", user_name, password)
24
+ end
25
+
26
+ def with_header(name, value)
27
+ headers.merge! name => value
28
+ end
29
+
30
+ def head(path)
31
+ begin
32
+ resp = http_accessor.resource(base_url + path).head(headers)
33
+ rescue Resourceful::UnsuccessfulHttpRequestError => e
34
+ resp = e.http_response
35
+ end
36
+
37
+ @last_response = ResourcefulResponse.new resp
38
+ end
39
+
40
+ def get(path)
41
+ begin
42
+ resp = http_accessor.resource(base_url + path).get(headers)
43
+ rescue Resourceful::UnsuccessfulHttpRequestError => e
44
+ resp = e.http_response
45
+ end
46
+
47
+ @last_response = ResourcefulResponse.new resp
48
+ end
49
+
50
+ def post(path, body = nil)
51
+ begin
52
+ resp = http_accessor.resource(base_url + path).post(body, headers)
53
+ rescue Resourceful::UnsuccessfulHttpRequestError => e
54
+ resp = e.http_response
55
+ end
56
+
57
+ @last_response = ResourcefulResponse.new resp
58
+ end
59
+
60
+ def put(path, body)
61
+ begin
62
+ resp = http_accessor.resource(base_url + path).put(body, headers)
63
+ rescue Resourceful::UnsuccessfulHttpRequestError => e
64
+ resp = e.http_response
65
+ end
66
+
67
+ @last_response = ResourcefulResponse.new resp
68
+ end
69
+
70
+ def delete(path)
71
+ begin
72
+ resp = http_accessor.resource(base_url + path).delete(headers)
73
+ rescue Resourceful::UnsuccessfulHttpRequestError => e
74
+ resp = e.http_response
75
+ end
76
+
77
+ @last_response = ResourcefulResponse.new resp
78
+ end
79
+
80
+ def response
81
+ last_response
82
+ end
83
+ end
84
+
85
+ end
@@ -0,0 +1,21 @@
1
+ require 'skink/client/response'
2
+
3
+ module Skink
4
+
5
+ class ResourcefulResponse < Client::Response
6
+ attr_reader :native_response
7
+
8
+ def initialize(native_response)
9
+ @native_response = native_response
10
+ end
11
+
12
+ def status_code
13
+ native_response.code
14
+ end
15
+
16
+ def body
17
+ native_response.body || ""
18
+ end
19
+ end
20
+
21
+ end
@@ -0,0 +1,3 @@
1
+ module Skink
2
+ VERSION = "0.1.0"
3
+ end
data/lib/skink.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "skink/version"
2
+
3
+ module Skink
4
+ class ConfigurationError < RuntimeError; end
5
+ end
data/skink.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'skink/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "skink"
8
+ gem.version = Skink::VERSION
9
+ gem.authors = ["Todd Thomas"]
10
+ gem.email = ["todd.thomas@openlogic.com"]
11
+ gem.description = %q{A DSL for testing REST APIs}
12
+ gem.summary = %q{Skink is Capybara's smaller, more primitive companion.}
13
+ gem.homepage = ""
14
+
15
+ gem.add_dependency "rack-test"
16
+ gem.add_dependency "openlogic-resourceful"
17
+
18
+ gem.add_development_dependency "rspec"
19
+ gem.add_development_dependency "sinatra"
20
+
21
+ gem.files = `git ls-files`.split($/)
22
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
23
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
24
+ gem.require_paths = ["lib"]
25
+ end
@@ -0,0 +1,65 @@
1
+ require_relative './spec_helper'
2
+
3
+ shared_examples "a REST API test language" do
4
+ it "is able to test a HEAD" do
5
+ head "/"
6
+ response.status_code.should == 200
7
+ response.body.size.should == 0
8
+ end
9
+
10
+ it "is able to test a GET" do
11
+ get "/"
12
+ response.status_code.should == 200
13
+ response.body.should == "Hello, world!"
14
+ end
15
+
16
+ it "is able to test a POST" do
17
+ body = "<ping/>"
18
+ with_content_type_header "application/xml"
19
+ post "/", body
20
+ response.status_code.should == 200
21
+ response.body.should == body
22
+ end
23
+
24
+ it "is able to test a PUT" do
25
+ body = "{\"ping\": \"hello\"}"
26
+ with_content_type_header "application/json"
27
+ put "/", body
28
+ response.status_code.should == 200
29
+ response.body.should == body
30
+ end
31
+
32
+ it "is able to test a DELETE" do
33
+ delete "/"
34
+ response.status_code.should == 200
35
+ response.body.should == "Deleted"
36
+ end
37
+
38
+ it "is able to test a request with additional headers" do
39
+ with_accept_header "application/json"
40
+ get "/foo"
41
+ response.status_code.should == 200
42
+ response.body.should == "{\"foo\": 42}"
43
+ end
44
+
45
+ it "is able to test requests which require basic authentication" do
46
+ get "/protected"
47
+ response.status_code.should == 401
48
+
49
+ with_basic_auth "admin", "admin"
50
+ get "/protected"
51
+ response.status_code.should == 200
52
+ response.body.should == "Welcome, authenticated client."
53
+ end
54
+ end
55
+
56
+ describe Skink::DSL do
57
+ context "using rack_test" do
58
+ it_behaves_like "a REST API test language"
59
+ end
60
+
61
+ context "using Resourceful" do
62
+ before { Skink.base_url = "http://localhost:4567" }
63
+ it_behaves_like "a REST API test language"
64
+ end
65
+ end
@@ -0,0 +1,27 @@
1
+ __DIR__ = File.dirname(__FILE__)
2
+
3
+ $LOAD_PATH << File.join(__DIR__, "..", "lib")
4
+
5
+ require 'skink/integrations/rspec'
6
+
7
+ RSpec.configure do |config|
8
+ config.include Skink::DSL
9
+ end
10
+
11
+ require 'test_server'
12
+ Skink.rack_app = Rack::Builder.new do
13
+ map "/" do
14
+ run Sinatra::Application
15
+ end
16
+ end.to_app
17
+
18
+ # Start "remote" test server in another thread.
19
+ @server = Thread.new do
20
+ Sinatra::Application.run!
21
+ end
22
+
23
+ # Kill the server process when rspec finishes.
24
+ at_exit { @server.exit }
25
+
26
+ # Let the server start up.
27
+ sleep 1
@@ -0,0 +1,40 @@
1
+ require 'sinatra'
2
+
3
+ helpers do
4
+ def protected!
5
+ unless authorized?
6
+ response['WWW-Authenticate'] = %(Basic realm="Restricted Area")
7
+ throw(:halt, [401, "Not authorized\n"])
8
+ end
9
+ end
10
+
11
+ def authorized?
12
+ @auth ||= Rack::Auth::Basic::Request.new(request.env)
13
+ @auth.provided? && @auth.basic? && @auth.credentials && @auth.credentials == ['admin', 'admin']
14
+ end
15
+ end
16
+
17
+ get '/' do
18
+ "Hello, world!"
19
+ end
20
+
21
+ post '/' do
22
+ request.body
23
+ end
24
+
25
+ put '/' do
26
+ request.body
27
+ end
28
+
29
+ delete '/' do
30
+ "Deleted"
31
+ end
32
+
33
+ get '/foo', :provides => :json do
34
+ "{\"foo\": 42}"
35
+ end
36
+
37
+ get '/protected' do
38
+ protected!
39
+ "Welcome, authenticated client."
40
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: skink
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Todd Thomas
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rack-test
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: openlogic-resourceful
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: sinatra
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: A DSL for testing REST APIs
79
+ email:
80
+ - todd.thomas@openlogic.com
81
+ executables: []
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - .gitignore
86
+ - Gemfile
87
+ - LICENSE.txt
88
+ - README.md
89
+ - Rakefile
90
+ - lib/skink.rb
91
+ - lib/skink/client/base.rb
92
+ - lib/skink/client/response.rb
93
+ - lib/skink/dsl.rb
94
+ - lib/skink/integrations/rails.rb
95
+ - lib/skink/integrations/rspec.rb
96
+ - lib/skink/rack_test_client/rack_test_client.rb
97
+ - lib/skink/rack_test_client/rack_test_response.rb
98
+ - lib/skink/resourceful_client/resourceful_client.rb
99
+ - lib/skink/resourceful_client/resourceful_response.rb
100
+ - lib/skink/version.rb
101
+ - skink.gemspec
102
+ - spec/skink_dsl_spec.rb
103
+ - spec/spec_helper.rb
104
+ - spec/test_server.rb
105
+ homepage: ''
106
+ licenses: []
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ! '>='
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ none: false
119
+ requirements:
120
+ - - ! '>='
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 1.8.24
126
+ signing_key:
127
+ specification_version: 3
128
+ summary: Skink is Capybara's smaller, more primitive companion.
129
+ test_files:
130
+ - spec/skink_dsl_spec.rb
131
+ - spec/spec_helper.rb
132
+ - spec/test_server.rb