sonar 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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