josh-rack-test 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,170 @@
1
+ require "uri"
2
+ module Rack
3
+ module Test
4
+
5
+ class Cookie
6
+ include Rack::Utils
7
+
8
+ # :api: private
9
+ attr_reader :name, :value
10
+
11
+ # :api: private
12
+ def initialize(raw, uri = nil, default_host = DEFAULT_HOST)
13
+ @default_host = default_host
14
+ uri ||= default_uri
15
+
16
+ # separate the name / value pair from the cookie options
17
+ @name_value_raw, options = raw.split(/[;,] */n, 2)
18
+
19
+ @name, @value = parse_query(@name_value_raw, ';').to_a.first
20
+ @options = parse_query(options, ';')
21
+
22
+ @options["domain"] ||= (uri.host || default_host)
23
+ @options["path"] ||= uri.path.sub(/\/[^\/]*\Z/, "")
24
+ end
25
+
26
+ def replaces?(other)
27
+ [name.downcase, domain, path] == [other.name.downcase, other.domain, other.path]
28
+ end
29
+
30
+ # :api: private
31
+ def raw
32
+ @name_value_raw
33
+ end
34
+
35
+ # :api: private
36
+ def empty?
37
+ @value.nil? || @value.empty?
38
+ end
39
+
40
+ # :api: private
41
+ def domain
42
+ @options["domain"]
43
+ end
44
+
45
+ def secure?
46
+ @options.has_key?("secure")
47
+ end
48
+
49
+ # :api: private
50
+ def path
51
+ @options["path"].strip || "/"
52
+ end
53
+
54
+ # :api: private
55
+ def expires
56
+ Time.parse(@options["expires"]) if @options["expires"]
57
+ end
58
+
59
+ # :api: private
60
+ def expired?
61
+ expires && expires < Time.now
62
+ end
63
+
64
+ # :api: private
65
+ def valid?(uri)
66
+ uri ||= default_uri
67
+
68
+ if uri.host.nil?
69
+ uri.host = @default_host
70
+ end
71
+
72
+ (!secure? || (secure? && uri.scheme == "https")) &&
73
+ uri.host =~ Regexp.new("#{Regexp.escape(domain)}$", Regexp::IGNORECASE) &&
74
+ uri.path =~ Regexp.new("^#{Regexp.escape(path)}")
75
+ end
76
+
77
+ # :api: private
78
+ def matches?(uri)
79
+ ! expired? && valid?(uri)
80
+ end
81
+
82
+ # :api: private
83
+ def <=>(other)
84
+ # Orders the cookies from least specific to most
85
+ [name, path, domain.reverse] <=> [other.name, other.path, other.domain.reverse]
86
+ end
87
+
88
+ protected
89
+
90
+ def default_uri
91
+ URI.parse("//" + @default_host + "/")
92
+ end
93
+
94
+ end
95
+
96
+ class CookieJar
97
+
98
+ # :api: private
99
+ def initialize(cookies = [], default_host = DEFAULT_HOST)
100
+ @default_host = default_host
101
+ @cookies = cookies
102
+ @cookies.sort!
103
+ end
104
+
105
+ def [](name)
106
+ cookies = hash_for(nil)
107
+ # TODO: Should be case insensitive
108
+ cookies[name] && cookies[name].value
109
+ end
110
+
111
+ def []=(name, value)
112
+ # TODO: needs proper escaping
113
+ merge("#{name}=#{value}")
114
+ end
115
+
116
+ def merge(raw_cookies, uri = nil)
117
+ return unless raw_cookies
118
+
119
+ raw_cookies = raw_cookies.split("\n") if raw_cookies.is_a? String
120
+ raw_cookies.each do |raw_cookie|
121
+ cookie = Cookie.new(raw_cookie, uri, @default_host)
122
+ self << cookie if cookie.valid?(uri)
123
+ end
124
+ end
125
+
126
+ def <<(new_cookie)
127
+ @cookies.reject! do |existing_cookie|
128
+ new_cookie.replaces?(existing_cookie)
129
+ end
130
+
131
+ @cookies << new_cookie
132
+ @cookies.sort!
133
+ end
134
+
135
+ # :api: private
136
+ def for(uri)
137
+ hash_for(uri).values.map { |c| c.raw }.join(';')
138
+ end
139
+
140
+ def to_hash
141
+ cookies = {}
142
+
143
+ hash_for(nil).each do |name, cookie|
144
+ cookies[name] = cookie.value
145
+ end
146
+
147
+ return cookies
148
+ end
149
+
150
+ protected
151
+
152
+ def hash_for(uri = nil)
153
+ cookies = {}
154
+
155
+ # The cookies are sorted by most specific first. So, we loop through
156
+ # all the cookies in order and add it to a hash by cookie name if
157
+ # the cookie can be sent to the current URI. It's added to the hash
158
+ # so that when we are done, the cookies will be unique by name and
159
+ # we'll have grabbed the most specific to the URI.
160
+ @cookies.each do |cookie|
161
+ cookies[cookie.name] = cookie if cookie.matches?(uri)
162
+ end
163
+
164
+ return cookies
165
+ end
166
+
167
+ end
168
+
169
+ end
170
+ end
@@ -0,0 +1,73 @@
1
+ require "forwardable"
2
+
3
+ module Rack
4
+ module Test
5
+ module Methods
6
+ extend Forwardable
7
+
8
+ def rack_mock_session(name = :default)
9
+ return build_rack_mock_session unless name
10
+
11
+ @_rack_mock_sessions ||= {}
12
+ @_rack_mock_sessions[name] ||= build_rack_mock_session
13
+ end
14
+
15
+ def build_rack_mock_session
16
+ Rack::MockSession.new(app)
17
+ end
18
+
19
+ def rack_test_session(name = :default)
20
+ return build_rack_test_session(name) unless name
21
+
22
+ @_rack_test_sessions ||= {}
23
+ @_rack_test_sessions[name] ||= build_rack_test_session(name)
24
+ end
25
+
26
+ def build_rack_test_session(name)
27
+ Rack::Test::Session.new(rack_mock_session(name))
28
+ end
29
+
30
+ def current_session
31
+ rack_test_session(_current_session_names.last)
32
+ end
33
+
34
+ def with_session(name)
35
+ _current_session_names.push(name)
36
+ yield rack_test_session(name)
37
+ _current_session_names.pop
38
+ end
39
+
40
+ def _current_session_names
41
+ @_current_session_names ||= [:default]
42
+ end
43
+
44
+ METHODS = [
45
+ :request,
46
+
47
+ # HTTP verbs
48
+ :get,
49
+ :post,
50
+ :put,
51
+ :delete,
52
+ :head,
53
+
54
+ # Redirects
55
+ :follow_redirect!,
56
+
57
+ # Header-related features
58
+ :header,
59
+ :set_cookie,
60
+ :clear_cookies,
61
+ :authorize,
62
+ :basic_authorize,
63
+ :digest_authorize,
64
+
65
+ # Expose the last request and response
66
+ :last_response,
67
+ :last_request
68
+ ]
69
+
70
+ def_delegators :current_session, *METHODS
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,27 @@
1
+ module Rack
2
+ module Test
3
+
4
+ class MockDigestRequest
5
+ def initialize(params)
6
+ @params = params
7
+ end
8
+
9
+ def method_missing(sym)
10
+ if @params.has_key? k = sym.to_s
11
+ return @params[k]
12
+ end
13
+
14
+ super
15
+ end
16
+
17
+ def method
18
+ @params['method']
19
+ end
20
+
21
+ def response(password)
22
+ Rack::Auth::Digest::MD5.new(nil).send :digest, self, password
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,36 @@
1
+ require "tempfile"
2
+
3
+ module Rack
4
+ module Test
5
+
6
+ class UploadedFile
7
+ # The filename, *not* including the path, of the "uploaded" file
8
+ attr_reader :original_filename
9
+
10
+ # The content type of the "uploaded" file
11
+ attr_accessor :content_type
12
+
13
+ def initialize(path, content_type = "text/plain", binary = false)
14
+ raise "#{path} file does not exist" unless ::File.exist?(path)
15
+ @content_type = content_type
16
+ @original_filename = ::File.basename(path)
17
+ @tempfile = Tempfile.new(@original_filename)
18
+ @tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
19
+ @tempfile.binmode if binary
20
+ FileUtils.copy_file(path, @tempfile.path)
21
+ end
22
+
23
+ def path
24
+ @tempfile.path
25
+ end
26
+
27
+ alias_method :local_path, :path
28
+
29
+ def method_missing(method_name, *args, &block) #:nodoc:
30
+ @tempfile.__send__(method_name, *args, &block)
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,99 @@
1
+ module Rack
2
+ module Test
3
+
4
+ module Utils
5
+ include Rack::Utils
6
+
7
+ def build_nested_query(value, prefix = nil)
8
+ case value
9
+ when Array
10
+ value.map do |v|
11
+ build_nested_query(v, "#{prefix}[]")
12
+ end.join("&")
13
+ when Hash
14
+ value.map do |k, v|
15
+ build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
16
+ end.join("&")
17
+ else
18
+ "#{prefix}=#{escape(value)}"
19
+ end
20
+ end
21
+
22
+ module_function :build_nested_query
23
+
24
+ def build_multipart(params, first = true)
25
+ if first
26
+ unless params.is_a?(Hash)
27
+ raise ArgumentError, "value must be a Hash"
28
+ end
29
+
30
+ multipart = false
31
+ query = lambda { |value|
32
+ case value
33
+ when Array
34
+ value.each(&query)
35
+ when Hash
36
+ value.values.each(&query)
37
+ when UploadedFile
38
+ multipart = true
39
+ end
40
+ }
41
+ params.values.each(&query)
42
+ return nil unless multipart
43
+ end
44
+
45
+ flattened_params = Hash.new
46
+
47
+ params.each do |key, value|
48
+ k = first ? key.to_s : "[#{key}]"
49
+
50
+ case value
51
+ when Array
52
+ value.map { |v|
53
+ build_multipart(v, false).each { |subkey, subvalue|
54
+ flattened_params["#{k}[]#{subkey}"] = subvalue
55
+ }
56
+ }
57
+ when Hash
58
+ build_multipart(value, false).each { |subkey, subvalue|
59
+ flattened_params[k + subkey] = subvalue
60
+ }
61
+ else
62
+ flattened_params[k] = value
63
+ end
64
+ end
65
+
66
+ if first
67
+ flattened_params.map { |name, file|
68
+ if file.respond_to?(:original_filename)
69
+ ::File.open(file.path, "rb") do |f|
70
+ f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
71
+ <<-EOF
72
+ --#{MULTIPART_BOUNDARY}\r
73
+ Content-Disposition: form-data; name="#{name}"; filename="#{escape(file.original_filename)}"\r
74
+ Content-Type: #{file.content_type}\r
75
+ Content-Length: #{::File.stat(file.path).size}\r
76
+ \r
77
+ #{f.read}\r
78
+ EOF
79
+ end
80
+ else
81
+ <<-EOF
82
+ --#{MULTIPART_BOUNDARY}\r
83
+ Content-Disposition: form-data; name="#{name}"\r
84
+ \r
85
+ #{file}\r
86
+ EOF
87
+ end
88
+ }.join + "--#{MULTIPART_BOUNDARY}--\r"
89
+ else
90
+ flattened_params
91
+ end
92
+ end
93
+
94
+ module_function :build_multipart
95
+
96
+ end
97
+
98
+ end
99
+ end
data/rack-test.gemspec ADDED
@@ -0,0 +1,70 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{rack-test}
8
+ s.version = "0.4.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Bryan Helmkamp"]
12
+ s.date = %q{2009-08-06}
13
+ s.email = %q{bryan@brynary.com}
14
+ s.extra_rdoc_files = [
15
+ "MIT-LICENSE.txt",
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ ".document",
20
+ ".gitignore",
21
+ "History.txt",
22
+ "MIT-LICENSE.txt",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "lib/rack/mock_session.rb",
26
+ "lib/rack/test.rb",
27
+ "lib/rack/test/cookie_jar.rb",
28
+ "lib/rack/test/methods.rb",
29
+ "lib/rack/test/mock_digest_request.rb",
30
+ "lib/rack/test/uploaded_file.rb",
31
+ "lib/rack/test/utils.rb",
32
+ "rack-test.gemspec",
33
+ "spec/fixtures/config.ru",
34
+ "spec/fixtures/fake_app.rb",
35
+ "spec/fixtures/foo.txt",
36
+ "spec/rack/test/cookie_spec.rb",
37
+ "spec/rack/test/digest_auth_spec.rb",
38
+ "spec/rack/test/multipart_spec.rb",
39
+ "spec/rack/test/utils_spec.rb",
40
+ "spec/rack/test_spec.rb",
41
+ "spec/rcov.opts",
42
+ "spec/spec.opts",
43
+ "spec/spec_helper.rb"
44
+ ]
45
+ s.homepage = %q{http://github.com/brynary/rack-test}
46
+ s.rdoc_options = ["--charset=UTF-8"]
47
+ s.require_paths = ["lib"]
48
+ s.rubyforge_project = %q{rack-test}
49
+ s.rubygems_version = %q{1.3.4}
50
+ s.summary = %q{Simple testing API built on Rack}
51
+ s.test_files = [
52
+ "spec/fixtures/fake_app.rb",
53
+ "spec/rack/test/cookie_spec.rb",
54
+ "spec/rack/test/digest_auth_spec.rb",
55
+ "spec/rack/test/multipart_spec.rb",
56
+ "spec/rack/test/utils_spec.rb",
57
+ "spec/rack/test_spec.rb",
58
+ "spec/spec_helper.rb"
59
+ ]
60
+
61
+ if s.respond_to? :specification_version then
62
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
63
+ s.specification_version = 3
64
+
65
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
66
+ else
67
+ end
68
+ else
69
+ end
70
+ end