sonar 0.2.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.
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - jruby-19mode
6
+ - rbx-19mode
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+
2
+ Copyright (c) 2012-2013 Walter Smith <waltee.smith@gmail.com>
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"),
6
+ to deal in the Software without restriction, including without limitation
7
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
8
+ and/or sell copies of the Software, and to permit persons to whom the Software
9
+ is furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,283 @@
1
+
2
+ <a href="https://travis-ci.org/waltee/sonar">
3
+ <img src="https://travis-ci.org/waltee/sonar.png" align="right"></a>
4
+
5
+ # Sonar
6
+
7
+ **API for Testing Rack Apps with easy**
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ $ [sudo] gem install sonar
13
+ ```
14
+
15
+ ## Load
16
+
17
+ ```ruby
18
+ require 'sonar'
19
+ ```
20
+
21
+ ## Use
22
+
23
+ Simply `include Sonar` in your tests.
24
+
25
+ Or use it directly, by initialize a session via `SonarSession.new`
26
+
27
+ ## App
28
+
29
+ When mixin used, call `app RackApp` inside testing suite to set app to be tested.
30
+
31
+ **Minitest Example:**
32
+
33
+ ```ruby
34
+ require 'sonar'
35
+
36
+ class MyTests < MiniTest::Unit::TestCase
37
+
38
+ include Sonar
39
+
40
+ def setup
41
+ app MyRackApp
42
+ end
43
+
44
+ def test
45
+ get '/url'
46
+ assert_equal last_response.status, 200
47
+ end
48
+ end
49
+ ```
50
+
51
+ **[Specular](https://github.com/waltee/specular) Example:**
52
+
53
+ ```ruby
54
+ Spec.new do
55
+ app MyRackApp
56
+
57
+ get '/url'
58
+ expect(last_response.status) == 200
59
+ end
60
+ ```
61
+
62
+ Multiple apps can be tested within same suite.<br/>
63
+ Each app will run own session.
64
+
65
+ **Minitest Example:**
66
+
67
+ ```ruby
68
+ require 'sonar'
69
+
70
+ class MyTests < MiniTest::Unit::TestCase
71
+
72
+ include Sonar
73
+
74
+ def setup
75
+ app MyRackApp
76
+ end
77
+
78
+ def test
79
+ # querying default app
80
+ get '/url'
81
+ assert_equal last_response.status, 200
82
+
83
+ # testing ForumApp
84
+ app ForumApp
85
+ get '/posts'
86
+ assert_equal last_response.status, 200
87
+
88
+ # back to default app
89
+ app MyRackApp
90
+ get '/url'
91
+ assert_equal last_response.status, 200
92
+ end
93
+ end
94
+ ```
95
+
96
+ When using session manually, you should set app at initialization.
97
+
98
+ **Example:**
99
+
100
+ ```ruby
101
+ session = SonarSession.new MyRackApp
102
+ session.get '/url'
103
+ assert_equal session.last_response.status, 200
104
+ ```
105
+
106
+
107
+ ## Resetting App
108
+
109
+ Sometimes you need to start over with a new app in pristine state, i.e. no cookies, no headers etc.
110
+
111
+ To achieve this, simply call `reset_app!` (or `reset_browser!`).
112
+
113
+ This will reset currently tested app. Other tested apps will stay untouched.
114
+
115
+ When creating sessions manually, app can NOT be switched/reset.<br/>
116
+ To test another app, simply create another session.
117
+
118
+ ## Requests
119
+
120
+ Use one of `get`, `post`, `put`, `patch`, `delete`, `options`, `head`
121
+ to make requests via Sonar browser.
122
+
123
+ To make a secure request, add `s_` prefix:
124
+
125
+ ```ruby
126
+ s_get '/path'
127
+ s_post '/path'
128
+ # etc.
129
+ ```
130
+
131
+ To make a request via XHR, aka Ajax, add `_x` suffix:
132
+
133
+ ```ruby
134
+ get_x '/path'
135
+ post_x '/path'
136
+ # etc.
137
+ ```
138
+
139
+ To make a secure request via XHR, add both `s_` and `_x`:
140
+
141
+ ```ruby
142
+ s_get_x '/path'
143
+ s_post_x '/path'
144
+ # etc.
145
+ ```
146
+
147
+ In terms of arguments, making HTTP requests via Sonar is identical to calling regular Ruby methods.<br/>
148
+ That's it, you do not need to join parameters into a string.<br/>
149
+ Just pass them as usual arguments:
150
+
151
+ ```ruby
152
+ post '/news', :create, :title => rand
153
+ post '/news', :update, id, :title => rand
154
+ get '/news', :delete, id
155
+ ```
156
+
157
+ ## Map
158
+
159
+ Previous example works just fine, however it is redundant and inconsistent.<br/>
160
+ Just imagine that tested app changed its base URL from /news to /headlines.
161
+
162
+ The solution is simple.<br/>
163
+ Use `map` to define a base URL that will be prepended to each request,<br/>
164
+ except ones starting with a slash or a protocol(http://, https:// etc.) of course.
165
+
166
+ ```ruby
167
+ Spec.new do
168
+ app MyRackApp
169
+ map '/news'
170
+
171
+ post :create, :title => rand
172
+ post :update, id, :title => rand
173
+ get :delete, id
174
+ end
175
+ ```
176
+
177
+ **Note:** requests starting with a slash or protocol(http://, https:// etc.)
178
+ wont use base URL defined by `map`.<br/>
179
+
180
+ **Note:** when you switching tested app, make sure you also change the map.
181
+
182
+ To disable mapping, simply call `map nil`
183
+
184
+ ## Cookies
185
+
186
+ **Set cookies:**
187
+
188
+ ```ruby
189
+ cookies['name'] = 'value'
190
+ ```
191
+
192
+ **Read cookies:**
193
+
194
+ ```ruby
195
+ cookie = cookies['name']
196
+ ```
197
+
198
+ **Delete a cookie:**
199
+
200
+ ```ruby
201
+ cookies.delete 'name'
202
+ ```
203
+
204
+
205
+ **Clear all cookies:**
206
+
207
+ ```ruby
208
+ cookies.clear
209
+ ```
210
+
211
+ Each app uses its own cookies jar.
212
+
213
+ ## Headers
214
+
215
+ Sonar allow to set headers that will be sent to app on all consequent requests.
216
+
217
+ **Set headers:**
218
+
219
+ ```ruby
220
+ header['User-Agent'] = 'Sonar'
221
+ header['Content-Type'] = 'text/plain'
222
+ header['rack.input'] = 'someString'
223
+ # etc.
224
+ ```
225
+
226
+ **Read headers:**
227
+
228
+ ```ruby
229
+ header = headers['User-Agent']
230
+ # etc.
231
+ ```
232
+
233
+ **Delete a header:**
234
+
235
+ ```ruby
236
+ headers.delete 'User-Agent'
237
+ ```
238
+
239
+ **Clear all headers:**
240
+
241
+ ```ruby
242
+ headers.clear
243
+ ```
244
+
245
+ Each app uses its own headers.
246
+
247
+ ## Authorization
248
+
249
+ **Basic Auth:**
250
+
251
+ ```ruby
252
+ authorize 'user', 'pass'
253
+ ```
254
+
255
+ **Reset earlier set Basic authorization header:**
256
+
257
+ ```ruby
258
+ reset_basic_auth!
259
+ ```
260
+
261
+ **Digest Auth:**
262
+
263
+ ```ruby
264
+ digest_authorize 'user', 'pass'
265
+ ```
266
+
267
+ **Reset earlier set Digest authorization header:**
268
+
269
+ ```ruby
270
+ reset_digest_auth!
271
+ ```
272
+
273
+ **Reset ANY earlier set authorization header:**
274
+
275
+ ```ruby
276
+ reset_auth!
277
+ ```
278
+
279
+ ## Follow Redirects
280
+
281
+ By default, Sonar wont follow redirects.
282
+
283
+ If last response is a redirect and you want Sonar to follow it, use `follow_redirect!`
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'bundler/gem_helper'
4
+
5
+ task default: :test
6
+
7
+ require './test/setup'
8
+
9
+ desc 'Run all tests'
10
+ task :test do
11
+ ::Dir['./test/test__*.rb'].each { |f| require f }
12
+ session = Specular.new
13
+ session.boot { include Sonar }
14
+ session.before do |app|
15
+ if app && app.respond_to?(:base_url)
16
+ app(app)
17
+ map(app.base_url)
18
+ get
19
+ end
20
+ end
21
+ session.run /SonarTest/, trace: true
22
+ puts session.failures if session.failed?
23
+ puts session.summary
24
+ session.exit_code
25
+ end
26
+
27
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,97 @@
1
+ require 'rubygems'
2
+ require 'uri'
3
+ require 'rack'
4
+
5
+
6
+ module SonarConstants
7
+
8
+ REQUEST_METHODS = %w[GET POST PUT PATCH DELETE OPTIONS HEAD TRACE].freeze
9
+ SESSION_METHODS = %w[
10
+ header headers cookies last_request last_response
11
+ auth authorize basic_authorize digest_auth digest_authorize
12
+ reset_auth! reset_basic_auth! reset_digest_auth!
13
+ ].freeze
14
+ DEFAULT_HOST = 'sonar.org'.freeze
15
+ DEFAULT_ENV = {'REMOTE_ADDR' => '127.0.0.1', 'HTTP_HOST' => DEFAULT_HOST}.freeze
16
+ end
17
+
18
+ module Sonar
19
+
20
+ # switch session
21
+ #
22
+ # sonar using app based sessions, that's it, creates sessions based on app __id__.
23
+ # you can test multiple apps and use `app RackApp` to switch between them.
24
+ #
25
+ def app app = nil
26
+ @__sonar__app__ = app if app
27
+ @__sonar__app__
28
+ end
29
+
30
+ def map *args
31
+ @__sonar__base_url__ = args.first if args.size > 0
32
+ @__sonar__base_url__
33
+ end
34
+
35
+ # reset session for current app.
36
+ # everything will be reset - cookies, headers, authorizations etc.
37
+ def reset_app!
38
+ __sonar__session__ :reset
39
+ end
40
+
41
+ alias reset_browser! reset_app!
42
+
43
+ ::SonarConstants::REQUEST_METHODS.each do |request_method|
44
+ define_method request_method.downcase do |*args|
45
+ params = args.last.is_a?(Hash) ? args.pop : {}
46
+ request :http, request_method, args.compact.join('/'), params
47
+ end
48
+ # secure
49
+ define_method 's_%s' % request_method.downcase do |*args|
50
+ params = args.last.is_a?(Hash) ? args.pop : {}
51
+ request :https, request_method, args.compact.join('/'), params
52
+ end
53
+ # xhr
54
+ define_method '%s_x' % request_method.downcase do |*args|
55
+ params = args.last.is_a?(Hash) ? args.pop : {}
56
+ request :http, request_method, args.compact.join('/'), params, {'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest'}
57
+ end
58
+ # secure xhr
59
+ define_method 's_%s_x' % request_method.downcase do |*args|
60
+ params = args.last.is_a?(Hash) ? args.pop : {}
61
+ request :https, request_method, args.compact.join('/'), params, {'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest'}
62
+ end
63
+ end
64
+
65
+ ::SonarConstants::SESSION_METHODS.each do |m|
66
+ define_method m do |*args|
67
+ __sonar__session__.send m, *args
68
+ end
69
+ end
70
+
71
+ def request scheme, request_method, uri, params, env = {}
72
+ uri = [@__sonar__base_url__, uri].compact.join('/') unless uri =~ /\A\/|\A[\w|\d]+\:\/\//
73
+ uri = ::URI.parse(uri.gsub(/\A\/+/, '/'))
74
+ uri.scheme ||= scheme.to_s
75
+ uri.host ||= ::SonarConstants::DEFAULT_HOST
76
+ uri.path = '/' << uri.path unless uri.path =~ /\A\//
77
+ params.is_a?(Hash) && params.each_pair do |k, v|
78
+ (v.is_a?(Numeric) || v.is_a?(Symbol)) && params.update(k => v.to_s)
79
+ end
80
+ __sonar__session__.invoke_request request_method, uri, params, env
81
+ end
82
+
83
+ def follow_redirect!
84
+ last_response.redirect? ||
85
+ raise('Last response is not an redirect!')
86
+ scheme = last_request.env['HTTPS'] == 'on' ? 'https' : 'http'
87
+ request scheme, 'GET', last_response['Location'], {}, {'HTTP_REFERER' => last_request.url}
88
+ end
89
+
90
+ def __sonar__session__ reset = false
91
+ (@__sonar__session__ ||= {})[app.__id__] = ::SonarSession.new(app) if reset
92
+ (@__sonar__session__ ||= {})[app.__id__] ||= ::SonarSession.new(app)
93
+ end
94
+ end
95
+
96
+ require 'sonar/cookies'
97
+ require 'sonar/session'
@@ -0,0 +1,125 @@
1
+ class SonarCookies
2
+ include ::Rack::Utils
3
+
4
+ def initialize
5
+ @jar = {}
6
+ end
7
+
8
+ def jar uri, cookies = nil
9
+ host = (((uri && uri.host) || ::SonarConstants::DEFAULT_HOST).split('.')[-2..-1]||[]).join('.').downcase
10
+ @jar[host] = cookies if cookies
11
+ @jar[host] ||= []
12
+ end
13
+
14
+ def [] name
15
+ cookies = to_hash
16
+ cookies[name] && cookies[name].value
17
+ end
18
+
19
+ def []= name, value
20
+ persist '%s=%s' % [name, ::Rack::Utils.escape(value)]
21
+ end
22
+
23
+ def delete name
24
+ jar nil, jar(nil).reject { |c| c.name == name }
25
+ end
26
+
27
+ def clear
28
+ jar nil, []
29
+ end
30
+
31
+ %w[size empty?].each do |m|
32
+ define_method m do |*args|
33
+ jar(nil).send __method__, *args
34
+ end
35
+ end
36
+
37
+ def persist raw_cookies, uri = nil
38
+ return unless raw_cookies.is_a?(String)
39
+
40
+ # before adding new cookies, lets cleanup expired ones
41
+ jar uri, jar(uri).reject { |c| c.expired? }
42
+
43
+ raw_cookies = raw_cookies.strip.split("\n").reject { |c| c.empty? }
44
+
45
+ raw_cookies.each do |raw_cookie|
46
+ cookie = Cookie.new(raw_cookie, uri)
47
+ cookie.valid?(uri) || next
48
+ jar(uri, jar(uri).reject { |existing_cookie| cookie.replaces? existing_cookie })
49
+ jar(uri) << cookie
50
+ end
51
+ jar(uri).sort!
52
+ end
53
+
54
+ def to_s uri = nil
55
+ to_hash(uri).values.map { |c| c.raw }.join(';')
56
+ end
57
+
58
+ def to_hash uri = nil
59
+ jar(uri).inject({}) do |cookies, cookie|
60
+ cookies.merge((uri ? cookie.dispose_for?(uri) : true) ? {cookie.name => cookie} : {})
61
+ end
62
+ end
63
+
64
+ class Cookie
65
+ include ::Rack::Utils
66
+
67
+ attr_reader :raw, :name, :value, :domain, :path, :expires, :default_host
68
+
69
+ def initialize raw, uri
70
+ @default_host = ::SonarConstants::DEFAULT_HOST
71
+
72
+ uri ||= default_uri
73
+ uri.host ||= default_host
74
+
75
+ @raw, @options = raw.split(/[;,] */n, 2)
76
+ @name, @value = parse_query(@raw, ';').to_a.first
77
+ @options = parse_query(@options, ';')
78
+
79
+ @domain = @options['domain'] || uri.host || default_host
80
+ @domain = '.' << @domain unless @domain =~ /\A\./
81
+
82
+ @path = @options['path'] || uri.path.sub(/\/[^\/]*\Z/, '')
83
+
84
+ (expires = @options['expires']) && (@expires = ::Time.parse(expires))
85
+ end
86
+
87
+ def replaces? cookie
88
+ [name.downcase, domain, path] == [cookie.name.downcase, cookie.domain, cookie.path]
89
+ end
90
+
91
+ def empty?
92
+ value.nil? || value.empty?
93
+ end
94
+
95
+ def secure?
96
+ @options.has_key?('secure')
97
+ end
98
+
99
+ def expired?
100
+ expires && expires < ::Time.now.gmtime
101
+ end
102
+
103
+ def dispose_for? uri
104
+ expired? ? false : valid?(uri)
105
+ end
106
+
107
+ def valid? uri = nil
108
+ uri ||= default_uri
109
+ uri.host ||= default_host
110
+
111
+ (secure? ? uri.scheme == 'https' : true) &&
112
+ (uri.host =~ /#{::Regexp.escape(domain.sub(/\A\./, ''))}\Z/i) &&
113
+ (uri.path =~ /\A#{::Regexp.escape(path)}/)
114
+ end
115
+
116
+ def <=> cookie
117
+ [name, path, domain.reverse] <=> [cookie.name, cookie.path, cookie.domain.reverse]
118
+ end
119
+
120
+ private
121
+ def default_uri
122
+ ::URI.parse('//' << default_host << '/')
123
+ end
124
+ end
125
+ end