sonar 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,150 @@
1
+ class SonarSession
2
+
3
+ include ::Sonar
4
+
5
+ attr_reader :headers, :cookies, :last_request, :last_response
6
+ alias header headers
7
+
8
+ def initialize app
9
+ app.respond_to?(:call) ||
10
+ raise('app should be a valid Rack app')
11
+
12
+ @app = app
13
+ @headers, @cookies = {}, ::SonarCookies.new
14
+ end
15
+
16
+ def basic_authorize user, pass
17
+ @basic_auth = [user, pass]
18
+ end
19
+
20
+ alias authorize basic_authorize
21
+ alias auth basic_authorize
22
+
23
+ def digest_authorize user, pass
24
+ @digest_auth = [user, pass]
25
+ end
26
+
27
+ alias digest_auth digest_authorize
28
+
29
+ def reset_basic_auth!
30
+ @basic_auth = nil
31
+ end
32
+
33
+ def reset_digest_auth!
34
+ @digest_auth = nil
35
+ end
36
+
37
+ def reset_auth!
38
+ reset_basic_auth!
39
+ reset_digest_auth!
40
+ end
41
+
42
+ def invoke_request request_method, uri, params, env
43
+
44
+ default_env = ::SonarConstants::DEFAULT_ENV.dup.merge(env)
45
+ default_env.update :method => request_method
46
+ default_env.update :params => params
47
+ default_env.update 'HTTP_COOKIE' => cookies.to_s(uri)
48
+ default_env.update basic_auth_header
49
+
50
+ process_request uri, default_env
51
+
52
+ if @digest_auth && @last_response.status == 401 && (challenge = @last_response['WWW-Authenticate'])
53
+ default_env.update digest_auth_header(challenge, uri.path, request_method)
54
+ process_request uri, default_env
55
+ end
56
+
57
+ cookies.persist(@last_response.header['Set-Cookie'], uri)
58
+
59
+ @last_response.respond_to?(:finish) && @last_response.finish
60
+ @last_response
61
+ end
62
+
63
+ def __sonar__session__
64
+ self
65
+ end
66
+
67
+ def reset_app!
68
+ raise 'It makes no sense to use `%s` with manually created sessions. To test another app, just create a new session.' % __method__
69
+ end
70
+
71
+ alias reset_browser! reset_app!
72
+
73
+ def app *args
74
+ args.any? && raise('It makes no sense to use `%s` with manually created sessions. To test another app, just create a new session.' % __method__)
75
+ @app
76
+ end
77
+
78
+ private
79
+ def process_request uri, env
80
+ env = ::Rack::MockRequest.env_for(uri.to_s, env.dup)
81
+ explicit_env = headers_to_env
82
+ explicit_env['rack.input'] &&
83
+ env['REQUEST_METHOD'] == 'POST' && env.delete('CONTENT_TYPE')
84
+ env.update explicit_env
85
+
86
+ @last_request = ::Rack::Request.new(env)
87
+
88
+ # initializing params. do not remove! needed for nested params to work
89
+ @last_request.params
90
+
91
+ status, headers, body = app.call(@last_request.env)
92
+
93
+ @last_response = ::Rack::MockResponse.new(status, headers, body, env['rack.errors'].flush)
94
+ body.respond_to?(:close) && body.close
95
+ end
96
+
97
+ def headers_to_env
98
+ headers.keys.inject({}) do |headers, key|
99
+ value = headers()[key]
100
+ if (key =~ /\A[[:upper:]].*\-?[[:upper:]]?.*?/) && (key !~ /\AHTTP_|\ACONTENT_TYPE\Z/)
101
+ key = (key == 'Content-Type' ? '' : 'HTTP_') << key.upcase.gsub('-', '_')
102
+ end
103
+ headers.merge key => value
104
+ end
105
+ end
106
+
107
+ def basic_auth_header
108
+ (auth = @basic_auth) ?
109
+ {'HTTP_AUTHORIZATION' => 'Basic %s' % ["#{auth.first}:#{auth.last}"].pack("m*")} :
110
+ {}
111
+ end
112
+
113
+ def digest_auth_header challenge, uri, request_method
114
+ params = ::Rack::Auth::Digest::Params.parse(challenge.split(" ", 2).last)
115
+ params.merge!({
116
+ "username" => @digest_auth.first,
117
+ "nc" => "00000001",
118
+ "cnonce" => "nonsensenonce",
119
+ "uri" => uri,
120
+ "method" => request_method,
121
+ })
122
+ params["response"] = MockDigestRequest.new(params).response(@digest_auth.last)
123
+ {'HTTP_AUTHORIZATION' => 'Digest ' << params.map {|p| '%s="%s"' % p}.join(', ')}
124
+ end
125
+
126
+ class MockDigestRequest # stolen from rack-test
127
+
128
+ def initialize(params)
129
+ @params = params
130
+ end
131
+
132
+ def method_missing(sym)
133
+ if @params.has_key? k = sym.to_s
134
+ return @params[k]
135
+ end
136
+
137
+ super
138
+ end
139
+
140
+ def method
141
+ @params['method']
142
+ end
143
+
144
+ def response(password)
145
+ Rack::Auth::Digest::MD5.new(nil).send :digest, self, password
146
+ end
147
+
148
+ end
149
+
150
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ version = '0.2.1'
4
+ Gem::Specification.new do |s|
5
+
6
+ s.name = 'sonar'
7
+ s.version = version
8
+ s.authors = ['Walter Smith', 'Joran Kikke']
9
+ s.email = ['waltee.smith@gmail.com', 'joran.k@gmail.com']
10
+ s.homepage = 'https://github.com/dangerousbeans/sonar'
11
+ s.summary = 'sonar-%s' % version
12
+ s.description = 'API for Testing Rack Applications via Mock HTTP'
13
+
14
+ s.required_ruby_version = '>= 1.9.2'
15
+
16
+ s.add_dependency 'rack', '~> 1.5'
17
+
18
+ s.add_development_dependency 'rake', '~> 10'
19
+ s.add_development_dependency 'specular', '~> 0.2.2'
20
+ s.add_development_dependency 'bundler'
21
+
22
+ s.require_paths = ['lib']
23
+ s.files = Dir['**/{*,.[a-z]*}'].reject {|e| e =~ /\.(gem|lock)\Z/}
24
+ s.licenses = ['MIT']
25
+ end
@@ -0,0 +1,152 @@
1
+ require 'rubygems'
2
+ require 'specular'
3
+
4
+ $:.unshift ::File.expand_path '../../lib', __FILE__
5
+ require 'sonar'
6
+
7
+ class Air < ::Rack::Request
8
+ class << self
9
+
10
+ def use ware = nil, *args, &proc
11
+ @middleware ||= []
12
+ @middleware << [ware, args, proc] if ware
13
+ @middleware
14
+ end
15
+
16
+ def map
17
+ @map
18
+ end
19
+
20
+ def action_map
21
+ @action_map
22
+ end
23
+
24
+ def app root = nil
25
+ if root
26
+ @root = root
27
+ else
28
+ return @app if @app
29
+ end
30
+ map!
31
+
32
+ builder, app = ::Rack::Builder.new, self
33
+ use.each do |w|
34
+ ware, args, proc = w
35
+ builder.use ware, *args, &proc
36
+ end
37
+ map.each_key do |route|
38
+ builder.map route do
39
+ run lambda { |env| app.new(env).__air__response__ route }
40
+ end
41
+ end
42
+ @app = builder.to_app
43
+ end
44
+
45
+ alias to_app app
46
+
47
+ def call env
48
+ app.call env
49
+ end
50
+
51
+ def base_url
52
+ @root || '/'
53
+ end
54
+
55
+ alias baseurl base_url
56
+
57
+ private
58
+ def map!
59
+ @action_map = {}
60
+ @map = self.instance_methods(false).reject { |m| m.to_s =~ /^__air__/ }.inject({}) do |map, meth|
61
+ route, request_method = meth.to_s, 'GET'
62
+ SonarConstants::REQUEST_METHODS.each do |rm|
63
+ regex = /^#{rm}_/i
64
+ route =~ regex && (route = route.sub(regex, '')) && (request_method = rm.upcase) && break
65
+ end
66
+
67
+ {'____' => '.',
68
+ '___' => '-',
69
+ '__' => '/'}.each_pair { |f, t| route = route.gsub(f, t) }
70
+
71
+ arity = self.instance_method(meth).arity
72
+ setup = [meth, arity < 0 ? -arity - 1 : arity]
73
+ (map[rootify(route)] ||={})[request_method] = setup
74
+ (map[rootify] ||= {})[request_method] = setup if route == 'index'
75
+ @action_map[meth.to_sym] = rootify(route)
76
+ map
77
+ end
78
+ end
79
+
80
+ def rootify route = nil
81
+ ('/%s/%s' % [base_url, route]).gsub /\/+/, '/'
82
+ end
83
+ end
84
+
85
+ attr_reader :response
86
+
87
+ def __air__response__ route
88
+ rsp = catch :__air__halt__ do
89
+ @response = ::Rack::Response.new
90
+ rest_map = self.class.map[route] || halt(404)
91
+ action, required_parameters = rest_map[env['REQUEST_METHOD']] || halt(404)
92
+ arguments = env['PATH_INFO'].to_s.split('/').select { |c| c.size > 0 }
93
+ arguments.size == required_parameters || halt(404, '%s arguments expected, %s given' % [required_parameters, arguments.size])
94
+ response.body = [self.send(action, *arguments).to_s]
95
+ response
96
+ end
97
+ rsp['Content-Type'] ||= 'text/html'
98
+ rsp.finish
99
+ end
100
+
101
+ def base_url
102
+ self.class.base_url
103
+ end
104
+
105
+ def halt status, message = nil
106
+ response.status = status
107
+ response.body = [message] if message
108
+ throw :__air__halt__, response
109
+ end
110
+
111
+ def redirect action_or_path
112
+ response['Location'] = self.class.action_map[action_or_path] || action_or_path
113
+ response.status = 302
114
+ throw :__air__halt__, response
115
+ end
116
+
117
+ def permanent_redirect action_or_path
118
+ response['Location'] = self.class.action_map[action_or_path] || action_or_path
119
+ response.status = 301
120
+ throw :__air__halt__, response
121
+ end
122
+
123
+ def params
124
+ @__air__params__ ||= indifferent_params(super)
125
+ end
126
+
127
+ def get_params
128
+ @__air__get_params__ ||= indifferent_params(self.GET)
129
+ end
130
+
131
+ def post_params
132
+ @__air__post_params__ ||= indifferent_params(self.POST)
133
+ end
134
+
135
+ private
136
+ def indifferent_params(object)
137
+ case object
138
+ when Hash
139
+ new_hash = indifferent_hash
140
+ object.each { |key, value| new_hash[key] = indifferent_params(value) }
141
+ new_hash
142
+ when Array
143
+ object.map { |item| indifferent_params(item) }
144
+ else
145
+ object
146
+ end
147
+ end
148
+
149
+ def indifferent_hash
150
+ Hash.new { |hash, key| hash[key.to_s] if Symbol === key }
151
+ end
152
+ end
@@ -0,0 +1,71 @@
1
+ module SonarTest__app
2
+
3
+ class App < Air
4
+
5
+ def index
6
+ __method__
7
+ end
8
+
9
+ end
10
+
11
+ class JustAnotherApp < Air
12
+ def another
13
+ __method__
14
+ end
15
+ end
16
+
17
+ Spec.new App do
18
+
19
+ get
20
+ expect(last_response.status) == 200
21
+ expect(last_response.body) == 'index'
22
+
23
+ Testing :cookies do
24
+ cookies['foo'] = 'bar'
25
+ expect(cookies['foo']) == 'bar'
26
+ end
27
+
28
+ Testing :headers do
29
+ header['User-Agent'] = 'Sonar'
30
+ expect(headers['User-Agent']) == 'Sonar'
31
+ end
32
+
33
+
34
+ When 'switching app' do
35
+ app JustAnotherApp
36
+
37
+ It 'should use own cookies jar' do
38
+ is(cookies['foo']).nil?
39
+ end
40
+
41
+ And 'own headers' do
42
+ is(headers['User-Agent']).nil?
43
+ end
44
+
45
+ get
46
+ expect(last_response.status) == 404
47
+
48
+ get :another
49
+ expect(last_response.status) == 200
50
+ end
51
+
52
+ When 'switching back to default app' do
53
+ app App
54
+
55
+ It 'should have previously cookies set' do
56
+ expect(cookies['foo']) == 'bar'
57
+ end
58
+
59
+ And :headers do
60
+ expect(headers['User-Agent']) == 'Sonar'
61
+ end
62
+
63
+ get
64
+ expect(last_response.status) == 200
65
+
66
+ get :another
67
+ expect(last_response.status) == 404
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,144 @@
1
+ module SonarTest__auth
2
+
3
+ class Basic < Air
4
+
5
+ use Rack::Auth::Basic do |u, p|
6
+ [u, p] == ['user', 'pass']
7
+ end
8
+
9
+ def index
10
+ __method__
11
+ end
12
+
13
+ def post_index
14
+ __method__
15
+ end
16
+
17
+ end
18
+
19
+ class Digest < Air
20
+
21
+ use Rack::Auth::Digest::MD5, 'AccessRestricted', rand.to_s do |u|
22
+ {'digest-user' => 'digest-pass'}[u]
23
+ end
24
+
25
+ def index
26
+ __method__
27
+ end
28
+
29
+ def post_index
30
+ __method__
31
+ end
32
+
33
+ end
34
+
35
+ Spec.new self do
36
+
37
+ Testing :Basic do
38
+ app Basic
39
+
40
+ r = get
41
+ expect(r.status) == 401
42
+
43
+ auth 'user', 'pass'
44
+ r = get
45
+ expect(r.status) == 200
46
+ expect(r.body) == 'index'
47
+
48
+ o 'reset auth using `reset_auth!`'
49
+ reset_auth!
50
+
51
+ r = get
52
+ expect(r.status) == 401
53
+
54
+ o 'relogin'
55
+ auth 'user', 'pass'
56
+ r = get
57
+ expect(r.status) == 200
58
+ expect(r.body) == 'index'
59
+
60
+ o 'reset auth using `reset_basic_auth!`'
61
+ reset_basic_auth!
62
+
63
+ r = get
64
+ expect(r.status) == 401
65
+
66
+ o 'relogin'
67
+ auth 'user', 'pass'
68
+ r = get
69
+ expect(r.status) == 200
70
+ expect(r.body) == 'index'
71
+
72
+ Should 'fail with wrong credentials' do
73
+ reset_basic_auth!
74
+
75
+ auth 'bad', 'guy'
76
+ r = get
77
+ expect(r.status) == 401
78
+ end
79
+
80
+ Should 'auth via POST' do
81
+ reset_basic_auth!
82
+
83
+ auth 'user', 'pass'
84
+ r = post
85
+ expect(r.status) == 200
86
+ expect(r.body) == 'post_index'
87
+ end
88
+ end
89
+
90
+ Testing :Digest do
91
+ app Digest
92
+
93
+ r = get
94
+ expect(r.status) == 401
95
+
96
+ digest_auth 'digest-user', 'digest-pass'
97
+ r = get
98
+ expect(r.status) == 200
99
+ expect(r.body) == 'index'
100
+
101
+ o 'reset auth using `reset_auth!`'
102
+ reset_digest_auth!
103
+
104
+ r = get
105
+ expect(r.status) == 401
106
+
107
+ o 'relogin'
108
+ digest_auth 'digest-user', 'digest-pass'
109
+ r = get
110
+ expect(r.status) == 200
111
+ expect(r.body) == 'index'
112
+
113
+ o 'reset auth using `reset_basic_auth!`'
114
+ reset_digest_auth!
115
+
116
+ r = get
117
+ expect(r.status) == 401
118
+
119
+ o 'relogin'
120
+ digest_auth 'digest-user', 'digest-pass'
121
+ r = get
122
+ expect(r.status) == 200
123
+ expect(r.body) == 'index'
124
+
125
+ Should 'fail with wrong credentials' do
126
+ reset_digest_auth!
127
+
128
+ digest_auth 'bad', 'guy'
129
+ r = get
130
+ expect(r.status) == 401
131
+ end
132
+
133
+ Should 'auth via POST' do
134
+ reset_digest_auth!
135
+
136
+ digest_auth 'digest-user', 'digest-pass'
137
+ r = post
138
+ expect(r.status) == 200
139
+ expect(r.body) == 'post_index'
140
+ end
141
+ end
142
+
143
+ end
144
+ end