nitrous 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/Rakefile +14 -0
- data/VERSION +1 -0
- data/cmd_test +11 -0
- data/command_line.rb +53 -0
- data/example.rb +4 -0
- data/init.rb +1 -0
- data/lib/core_ext.rb +27 -0
- data/lib/nitrous.rb +5 -0
- data/lib/nitrous/assertions.rb +95 -0
- data/lib/nitrous/daemon.rb +35 -0
- data/lib/nitrous/daemon_controller.rb +11 -0
- data/lib/nitrous/http_io.rb +34 -0
- data/lib/nitrous/integration_test.rb +372 -0
- data/lib/nitrous/progress_bar.rb +82 -0
- data/lib/nitrous/rails_test.rb +77 -0
- data/lib/nitrous/server.rb +4 -0
- data/lib/nitrous/test.rb +121 -0
- data/lib/nitrous/test_block.rb +43 -0
- data/lib/nitrous/test_context.rb +76 -0
- data/lib/nitrous/test_result.rb +17 -0
- data/lib/rails_ext.rb +60 -0
- data/nitrous.gemspec +64 -0
- data/rails_env.rb +68 -0
- data/test/assertion_test.rb +31 -0
- data/test/test_test.rb +30 -0
- data/test_helper.rb +2 -0
- metadata +90 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |gemspec|
|
4
|
+
gemspec.name = "nitrous"
|
5
|
+
gemspec.summary = "A half-baked integration testing framework"
|
6
|
+
gemspec.description = ""
|
7
|
+
gemspec.email = "austin.taylor@gmail.com"
|
8
|
+
gemspec.homepage = "http://github.com/austintaylor/nitrous"
|
9
|
+
gemspec.authors = ["Austin Taylor", "Paul Nicholson"]
|
10
|
+
end
|
11
|
+
rescue LoadError
|
12
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
13
|
+
end
|
14
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.3
|
data/cmd_test
ADDED
data/command_line.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'activesupport'
|
3
|
+
class CommandLineUtility
|
4
|
+
class << self
|
5
|
+
def inherited(klass)
|
6
|
+
klass.descriptions = descriptions.dup
|
7
|
+
end
|
8
|
+
|
9
|
+
def run
|
10
|
+
return unless name.underscore == $0.split("/").last
|
11
|
+
instance = self.new
|
12
|
+
if ARGV.empty?
|
13
|
+
instance.default
|
14
|
+
else
|
15
|
+
instance.send(ARGV.first, *ARGV[1..-1])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def descriptions=(descriptions)
|
20
|
+
@descriptions = descriptions
|
21
|
+
end
|
22
|
+
|
23
|
+
def descriptions
|
24
|
+
@descriptions ||= HashWithIndifferentAccess.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def describe(command, description)
|
28
|
+
descriptions[command] = description
|
29
|
+
end
|
30
|
+
|
31
|
+
def description_for(command)
|
32
|
+
descriptions[command]
|
33
|
+
end
|
34
|
+
|
35
|
+
def commands
|
36
|
+
self.public_instance_methods.sort - Object.public_instance_methods - ["default"]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe :help, "Print this help"
|
41
|
+
def help
|
42
|
+
puts "usage: #{self.class.name.underscore} [COMMAND [ARGS]]"
|
43
|
+
puts ""
|
44
|
+
puts "Commands:"
|
45
|
+
stuff = self.class.commands.map do |command|
|
46
|
+
description = self.class.description_for(command)
|
47
|
+
description ? " #{command} (#{description})" : " #{command}"
|
48
|
+
end
|
49
|
+
puts stuff
|
50
|
+
end
|
51
|
+
|
52
|
+
alias_method :default, :help
|
53
|
+
end
|
data/example.rb
ADDED
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'nitrous/server'
|
data/lib/core_ext.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
if !Array.instance_methods.include?('sum')
|
2
|
+
class Array
|
3
|
+
def sum
|
4
|
+
inject(0) do |sum, each|
|
5
|
+
block_given? ? sum + yield(each) : sum + each
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Symbol
|
12
|
+
def to_proc
|
13
|
+
Proc.new { |*args| args.shift.__send__(self, *args) }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Exception
|
18
|
+
def test_output
|
19
|
+
to_s + "\n" + backtrace.join("\n")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Hash
|
24
|
+
def to_s
|
25
|
+
map {|k,v| "#{k}: #{v}"}.join("\r\n")
|
26
|
+
end
|
27
|
+
end
|
data/lib/nitrous.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
module Nitrous
|
2
|
+
class AssertionFailedError < Exception
|
3
|
+
def initialize(message, filename)
|
4
|
+
@message, @filename = message, filename
|
5
|
+
end
|
6
|
+
|
7
|
+
def failure_location
|
8
|
+
@failure_location ||= backtrace.detect do |line|
|
9
|
+
line.include?(@filename)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def snippet
|
14
|
+
failure_location =~ /^([^:]+):(\d+)/
|
15
|
+
index = $2.to_i - 1
|
16
|
+
lines = File.readlines($1)
|
17
|
+
"...\n" +
|
18
|
+
" " + lines[index - 1] +
|
19
|
+
" >>" + lines[index] +
|
20
|
+
" " + lines[index + 1] +
|
21
|
+
"...\n"
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_output
|
25
|
+
"Assertion failed on #{failure_location}\n#{@message}\n#{snippet}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module Assertions
|
30
|
+
def self.method_added(method)
|
31
|
+
return unless method.to_s =~ /!$/
|
32
|
+
name = method.to_s.gsub("!", '')
|
33
|
+
module_eval <<-"end;"
|
34
|
+
def #{name}(*args, &b)
|
35
|
+
collect_errors do
|
36
|
+
#{method}(*args, &b)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end;
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.included(mod)
|
43
|
+
mod.module_eval do
|
44
|
+
def self.method_added(method)
|
45
|
+
Assertions.method_added(method)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def fail(message)
|
51
|
+
raise AssertionFailedError.new(message, @current_test.filename)
|
52
|
+
end
|
53
|
+
|
54
|
+
def assert!(value, message=nil)
|
55
|
+
fail(message || "#{value.inspect} is not true.") unless value
|
56
|
+
yield if block_given?
|
57
|
+
end
|
58
|
+
|
59
|
+
def assert_nil!(value)
|
60
|
+
fail("#{value.inspect} is not nil.") unless value.nil?
|
61
|
+
yield if block_given?
|
62
|
+
end
|
63
|
+
|
64
|
+
def assert_equal!(expected, actual, message=nil)
|
65
|
+
fail(message || "Expected: <#{expected}> but was <#{actual}>") unless expected == actual
|
66
|
+
yield if block_given?
|
67
|
+
end
|
68
|
+
|
69
|
+
def assert_not_equal!(not_expected, actual, message=nil)
|
70
|
+
fail(message || "Expected: <#{not_expected}> not to equal <#{actual}>") unless not_expected != actual
|
71
|
+
yield if block_given?
|
72
|
+
end
|
73
|
+
|
74
|
+
def assert_match!(pattern, string, message=nil)
|
75
|
+
pattern = Regexp.new(Regexp.escape(pattern)) if pattern.is_a?(String)
|
76
|
+
fail(message || "#{string} expected to be =~ #{pattern}") unless string =~ pattern
|
77
|
+
end
|
78
|
+
|
79
|
+
def assert_raise!(type=Exception, &block)
|
80
|
+
yield
|
81
|
+
passed = true
|
82
|
+
raise
|
83
|
+
rescue Exception => e
|
84
|
+
fail("Expected a(n) #{type} to be raised but raised a(n) #{e.class}") if e.class != type
|
85
|
+
fail("Expected a(n) #{type} to be raised") if passed
|
86
|
+
end
|
87
|
+
|
88
|
+
def assert_not_raised!(type=Exception, &block)
|
89
|
+
yield
|
90
|
+
rescue type => e
|
91
|
+
fail("Expected a(n) #{type} not to be raised but a(n) #{e.class} was raised.\n#{e.message}")
|
92
|
+
end
|
93
|
+
alias_method :assert_nothing_raised!, :assert_not_raised!
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'webrick'
|
2
|
+
require 'fileutils'
|
3
|
+
require File.join(File.dirname(__FILE__), 'http_io')
|
4
|
+
FileUtils.cd(ARGV[0])
|
5
|
+
ENV["RAILS_ENV"] = "test"
|
6
|
+
require "config/environment"
|
7
|
+
|
8
|
+
module RailsEnv
|
9
|
+
def self.exit_blocks
|
10
|
+
@exit_blocks ||= []
|
11
|
+
end
|
12
|
+
end
|
13
|
+
module Kernel
|
14
|
+
def at_exit(&block)
|
15
|
+
RailsEnv.exit_blocks << block
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
server = WEBrick::HTTPServer.new(:Port => 4034)
|
20
|
+
server.mount_proc("/run_test") do |req, res|
|
21
|
+
# if pid = fork
|
22
|
+
# Process.wait(pid)
|
23
|
+
# else
|
24
|
+
# $stdout = Nitrous::HttpIO.new
|
25
|
+
# res.body = $stdout
|
26
|
+
io = Nitrous::HttpIO.new
|
27
|
+
res.body = io
|
28
|
+
io.write 'hello'
|
29
|
+
# load req.query['file']
|
30
|
+
# RailsEnv.exit_blocks.each(&:call)
|
31
|
+
io.close
|
32
|
+
# $stdout.close
|
33
|
+
# end
|
34
|
+
end
|
35
|
+
server.start
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'daemons'
|
3
|
+
|
4
|
+
RAILS_ROOT = ARGV.last
|
5
|
+
options = {
|
6
|
+
:ARGV => [RAILS_ROOT]
|
7
|
+
:app_name => 'nitrous_server',
|
8
|
+
:dir_mode => :normal,
|
9
|
+
:dir => File.join(RAILS_ROOT, 'tmp/pids/')
|
10
|
+
}
|
11
|
+
Daemons.run(File.join(File.dirname(__FILE__), 'daemon.rb'), options)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module Nitrous
|
4
|
+
class HttpIO
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@string = String.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def is_a?(klass)
|
11
|
+
klass == IO ? true : super(klass);
|
12
|
+
end
|
13
|
+
|
14
|
+
def size
|
15
|
+
0
|
16
|
+
end
|
17
|
+
|
18
|
+
def read(len=nil)
|
19
|
+
sleep 1 while @string.empty? && !@closed
|
20
|
+
return nil if @closed
|
21
|
+
string = @string.dup
|
22
|
+
@string = ""
|
23
|
+
string
|
24
|
+
end
|
25
|
+
|
26
|
+
def write(string)
|
27
|
+
@string << string
|
28
|
+
end
|
29
|
+
|
30
|
+
def close
|
31
|
+
@closed = true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,372 @@
|
|
1
|
+
require 'action_controller/assertions/selector_assertions'
|
2
|
+
require 'rails_ext'
|
3
|
+
require 'mime/types'
|
4
|
+
require 'active_support'
|
5
|
+
require 'action_controller'
|
6
|
+
require 'fileutils'
|
7
|
+
require 'patron'
|
8
|
+
module Nitrous
|
9
|
+
class IntegrationTest < RailsTest
|
10
|
+
SERVER_PORT = 4022
|
11
|
+
include ActionController::Assertions::SelectorAssertions
|
12
|
+
attr_accessor :cookies, :response, :status, :headers, :current_uri
|
13
|
+
at_exit {start_server}
|
14
|
+
|
15
|
+
def self.start_server
|
16
|
+
@server_thread = Thread.start do
|
17
|
+
options = {
|
18
|
+
:Port => SERVER_PORT,
|
19
|
+
:Host => "0.0.0.0",
|
20
|
+
:environment => (ENV['RAILS_ENV'] || "development").dup,
|
21
|
+
:config => RAILS_ROOT + "/config.ru",
|
22
|
+
:detach => false,
|
23
|
+
:debugger => false,
|
24
|
+
:path => nil
|
25
|
+
}
|
26
|
+
|
27
|
+
server = Rack::Handler::WEBrick
|
28
|
+
# begin
|
29
|
+
# server = Rack::Handler::Mongrel
|
30
|
+
# rescue LoadError => e
|
31
|
+
# end
|
32
|
+
|
33
|
+
ENV["RAILS_ENV"] = options[:environment]
|
34
|
+
RAILS_ENV.replace(options[:environment]) if defined?(RAILS_ENV)
|
35
|
+
|
36
|
+
if File.exist?(options[:config])
|
37
|
+
config = options[:config]
|
38
|
+
if config =~ /\.ru$/
|
39
|
+
cfgfile = File.read(config)
|
40
|
+
if cfgfile[/^#\\(.*)/]
|
41
|
+
opts.parse!($1.split(/\s+/))
|
42
|
+
end
|
43
|
+
inner_app = eval("Rack::Builder.new {( " + cfgfile + "\n )}.to_app", nil, config)
|
44
|
+
else
|
45
|
+
require config
|
46
|
+
inner_app = Object.const_get(File.basename(config, '.rb').capitalize)
|
47
|
+
end
|
48
|
+
else
|
49
|
+
require RAILS_ROOT + "/config/environment"
|
50
|
+
inner_app = ActionController::Dispatcher.new
|
51
|
+
end
|
52
|
+
|
53
|
+
app = Rack::Builder.new {
|
54
|
+
# use Rails::Rack::LogTailer unless options[:detach]
|
55
|
+
use Rails::Rack::Debugger if options[:debugger]
|
56
|
+
map '/' do
|
57
|
+
use Rails::Rack::Static
|
58
|
+
run inner_app
|
59
|
+
end
|
60
|
+
}.to_app
|
61
|
+
|
62
|
+
trap(:INT) { exit }
|
63
|
+
|
64
|
+
server.run(app, options.merge(:AccessLog => [], :Logger => WEBrick::Log.new("/dev/null")))
|
65
|
+
|
66
|
+
# Socket.do_not_reverse_lookup = true # patch for OS X
|
67
|
+
# server = WEBrick::HTTPServer.new(:BindAddress => '0.0.0.0', :ServerType => WEBrick::SimpleServer, :Port => 4022, :AccessLog => [], :Logger => WEBrick::Log.new("/dev/null"))
|
68
|
+
# server.mount('/', DispatchServlet, :server_root => File.expand_path(RAILS_ROOT + "/public/"))
|
69
|
+
# Rack::Handler::Mongrel.start(app, :Host => '0.0.0.0', :Port => 4022, :config => RAILS_ROOT + "/config.ru", :AccessLog => [])
|
70
|
+
end
|
71
|
+
sleep 0.001 until @server_thread.status == "sleep"
|
72
|
+
end
|
73
|
+
|
74
|
+
ActionController::Routing::Routes.install_helpers(self)
|
75
|
+
def url_for(options)
|
76
|
+
if options.delete(:only_path)
|
77
|
+
ActionController::Routing::Routes.generate(options)
|
78
|
+
else
|
79
|
+
"http://localhost:#{SERVER_PORT}" + ActionController::Routing::Routes.generate(options)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def navigate_to(path, headers={})
|
84
|
+
get path, nil, headers
|
85
|
+
follow_redirect! if redirect?
|
86
|
+
puts response.body if error?
|
87
|
+
assert !error?
|
88
|
+
end
|
89
|
+
|
90
|
+
BOUNDARY = 'multipart-boundary000'
|
91
|
+
def submit_form(id, data = {})
|
92
|
+
@redisplay = false
|
93
|
+
id, data = nil, id if id.is_a?(Hash)
|
94
|
+
form = css_select(id ? "form##{id}" : "form").first
|
95
|
+
fail(id ? "Form not found with id <#{id}>" : "No form found") unless form
|
96
|
+
validate = data.delete(:validate)
|
97
|
+
validate_form_fields(form, data) unless validate == false
|
98
|
+
fields = data.to_fields.reverse_merge(existing_values(form))
|
99
|
+
if form['enctype'] == 'multipart/form-data'
|
100
|
+
self.send(form["method"], form["action"], multipart_encode(fields), {'Content-Type' => "multipart/form-data, boundary=#{BOUNDARY}"})
|
101
|
+
else
|
102
|
+
self.send(form["method"], form["action"], fields)
|
103
|
+
end
|
104
|
+
puts response.body if error?
|
105
|
+
assert !error?
|
106
|
+
@redisplay = true if !redirect? && (id ? css_select("form##{id}").first : true)
|
107
|
+
follow_redirect! if redirect?
|
108
|
+
puts response.body if error?
|
109
|
+
assert !error?
|
110
|
+
end
|
111
|
+
|
112
|
+
def post_form(url, data={}, method = :post)
|
113
|
+
fields = data.to_fields
|
114
|
+
if fields.values.any? {|v| v.respond_to?(:read)}
|
115
|
+
self.send(method, url, multipart_encode(fields), {'Content-Type' => "multipart/form-data, boundary=#{BOUNDARY}"})
|
116
|
+
else
|
117
|
+
self.send(method, url, fields)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def multipart_encode(fields)
|
122
|
+
data = ""
|
123
|
+
fields.to_fields.each do |key, value|
|
124
|
+
data << "--#{BOUNDARY}\r\n"
|
125
|
+
if value.respond_to?(:read)
|
126
|
+
filename = File.basename(value.path)
|
127
|
+
data << "Content-Disposition: form-data; name=\"#{key}\"; filename=\"#{filename}\"\r\n"
|
128
|
+
data << "Content-Transfer-Encoding: binary\r\n"
|
129
|
+
data << "Content-Type: #{MIME::Types.type_for(filename)}\r\n\r\n"
|
130
|
+
data << value.read
|
131
|
+
else
|
132
|
+
data << "Content-Disposition: form-data; name=\"#{key}\"\r\n\r\n"
|
133
|
+
data << value.to_s
|
134
|
+
end
|
135
|
+
data << "\r\n"
|
136
|
+
end
|
137
|
+
data << "--#{BOUNDARY}--"
|
138
|
+
data
|
139
|
+
end
|
140
|
+
|
141
|
+
def click_link(url, method=:get)
|
142
|
+
if method == :delete
|
143
|
+
elements = css_select("*[href=#{url}][onclick]")
|
144
|
+
fail("No link found with url <#{url}> and method delete") if elements.empty? || !elements.any?{|element| element["onclick"] =~ /m.setAttribute\('name', '_method'\);.*?m.setAttribute\('value', 'delete'\);/}
|
145
|
+
delete url
|
146
|
+
follow_redirect! if redirect?
|
147
|
+
puts response.body if error?
|
148
|
+
assert !error?
|
149
|
+
else
|
150
|
+
fail("No link found with url <#{url}>") unless css_select("*[href=#{url}]").first
|
151
|
+
navigate_to(url)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def assert_form_redisplayed!
|
156
|
+
fail("Expected form to redisplay. Redirected to <#{current_uri}>") unless @redisplay
|
157
|
+
end
|
158
|
+
|
159
|
+
def field_value(name)
|
160
|
+
css_select(html_document.root, "input, select, textarea").detect {|field| field["name"] == name}["value"]
|
161
|
+
end
|
162
|
+
|
163
|
+
def assert_viewing(request_uri, message=nil)
|
164
|
+
fail("Expected page but recieved redirect. <#{current_uri}>") unless success?
|
165
|
+
assert_match %r(#{Regexp.escape(request_uri)}(\?|&|$)), current_uri, message
|
166
|
+
end
|
167
|
+
|
168
|
+
def assert_page_contains!(string)
|
169
|
+
fail("Expected page to contain <#{string}> but it did not. Page:\n#{response.body}") unless response.body.include?(string.to_s)
|
170
|
+
end
|
171
|
+
|
172
|
+
def assert_not_page_contains!(string)
|
173
|
+
fail("Expected page not to contain <#{string}> but it did. Page:\n#{response.body}") if response.body.include?(string.to_s)
|
174
|
+
end
|
175
|
+
|
176
|
+
def assert_form_values!(id, data={})
|
177
|
+
id, data = nil, id if id.is_a?(Hash)
|
178
|
+
form = css_select(id ? "form##{id}" : "form").first
|
179
|
+
fail(id ? "Form not found with id <#{id}>" : "No form found") unless form
|
180
|
+
data.to_fields.each do |name, value|
|
181
|
+
form_fields = css_select form, "input, select, textarea"
|
182
|
+
matching_fields = form_fields.select {|field| (field["name"] == name || field["name"] == "#{name}[]") && (!%w(radio checkbox).include?(field['type']) || field['checked'] == 'checked')}
|
183
|
+
|
184
|
+
# Handle boolean checkboxes
|
185
|
+
matching_field = matching_fields.detect {|f| f['checked'] == 'checked'} || matching_fields.first
|
186
|
+
|
187
|
+
fail "Could not find a form field having the name #{name}" unless matching_field
|
188
|
+
case matching_field.name.downcase
|
189
|
+
when 'input'
|
190
|
+
fail "Expected value of field #{name} to be #{value} but was #{matching_field['value']}" unless value.to_s == matching_field['value']
|
191
|
+
when 'textarea'
|
192
|
+
assert_equal value.to_s, matching_field.children.first.to_s
|
193
|
+
when 'select'
|
194
|
+
selected_option = css_select(matching_field, 'option[selected]').first
|
195
|
+
fail("No option selected for #{name}. Expected #{value} to be selected.") unless selected_option
|
196
|
+
assert_equal value.to_s, selected_option['value']
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def existing_values(form)
|
202
|
+
inputs = css_select(form, 'input').reject {|i| %w(checkbox radio).include?(i['type']) && (i['checked'].blank? || i['checked'].downcase != 'checked')}
|
203
|
+
values = {}
|
204
|
+
inputs.each do |input|
|
205
|
+
values[input['name']] = input['value']
|
206
|
+
end
|
207
|
+
css_select(form, 'textarea').each do |textarea|
|
208
|
+
values[textarea['name']] = textarea.children.map(&:to_s).join
|
209
|
+
end
|
210
|
+
css_select(form, 'select').each do |select|
|
211
|
+
selected = css_select(select, 'option[selected]').first || css_select(select, 'option').first
|
212
|
+
values[select['name']] = selected['value'] if selected
|
213
|
+
end
|
214
|
+
values.each{|k, v| values[k] = '' if v.nil?}
|
215
|
+
end
|
216
|
+
|
217
|
+
def validate_form_fields(form, data)
|
218
|
+
data.to_fields.each do |name, value|
|
219
|
+
form_fields = css_select form, "input, select, textarea"
|
220
|
+
matching_field = form_fields.detect {|field| field["name"] == name || field["name"] == "#{name}[]"}
|
221
|
+
fail "Could not find a form field having the name #{name}" unless matching_field
|
222
|
+
assert_equal 'file', matching_field['type'] if value.is_a?(File)
|
223
|
+
assert_equal "multipart/form-data", form["enctype"], "Form <#{form['id']}> has a file field <#{name}>, but the enctype is not multipart/form-data" if matching_field["type"] == "file"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def view(email)
|
228
|
+
@response = ActionController::TestResponse.new
|
229
|
+
@response.body = email.body
|
230
|
+
@html_document = nil
|
231
|
+
end
|
232
|
+
|
233
|
+
def follow_redirect!
|
234
|
+
raise "not a redirect! #{@status} #{@status_message}" unless redirect?
|
235
|
+
|
236
|
+
location = URI.parse(headers['location'].first)
|
237
|
+
path = location.query ? "#{location.path}?#{location.query}" : location.path
|
238
|
+
domains = location.host.split('.')
|
239
|
+
subdomain = domains.length > 2 ? domains.first : nil
|
240
|
+
set_subdomain(subdomain) if subdomain != @subdomain
|
241
|
+
|
242
|
+
get(location.host.include?('localhost') ? path : headers['location'].first)
|
243
|
+
status
|
244
|
+
end
|
245
|
+
|
246
|
+
def html_document
|
247
|
+
xml = @response.content_type =~ /xml$/
|
248
|
+
@html_document ||= HTML::Document.new(@response.body, false, xml)
|
249
|
+
end
|
250
|
+
|
251
|
+
def get(path, parameters=nil, headers={})
|
252
|
+
headers['QUERY_STRING'] = requestify(parameters) || ""
|
253
|
+
process(headers, path) do
|
254
|
+
if(!headers['QUERY_STRING'].blank?)
|
255
|
+
http_session.get(path + "?#{headers['QUERY_STRING']}", headers)
|
256
|
+
else
|
257
|
+
http_session.get(path, headers)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def post(path, parameters=nil, headers={})
|
263
|
+
data = requestify(parameters) || ""
|
264
|
+
headers['CONTENT_LENGTH'] = data.length.to_s
|
265
|
+
process(headers, path) do
|
266
|
+
http_session.post(path, data, headers)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def delete(path, parameters=nil, headers={})
|
271
|
+
headers['QUERY_STRING'] = requestify(parameters) || ""
|
272
|
+
process(headers, path) do
|
273
|
+
http_session.delete(path, headers)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def put(path, parameters=nil, headers={})
|
278
|
+
data = requestify(parameters) || ""
|
279
|
+
headers['CONTENT_LENGTH'] = data.length.to_s
|
280
|
+
process(headers, path) do
|
281
|
+
http_session.put(path, data, headers)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def process(headers, path=nil)
|
286
|
+
headers['Cookie'] = encode_cookies unless encode_cookies.blank?
|
287
|
+
self.response = yield
|
288
|
+
self.current_uri = path
|
289
|
+
@html_document = nil
|
290
|
+
parse_result
|
291
|
+
end
|
292
|
+
|
293
|
+
# was the response successful?
|
294
|
+
def success?
|
295
|
+
status == 200
|
296
|
+
end
|
297
|
+
|
298
|
+
# was the URL not found?
|
299
|
+
def missing?
|
300
|
+
status == 404
|
301
|
+
end
|
302
|
+
|
303
|
+
# were we redirected?
|
304
|
+
def redirect?
|
305
|
+
(300..399).include?(status)
|
306
|
+
end
|
307
|
+
|
308
|
+
# was there a server-side error?
|
309
|
+
def error?
|
310
|
+
(500..599).include?(status)
|
311
|
+
end
|
312
|
+
|
313
|
+
private
|
314
|
+
|
315
|
+
def encode_cookies
|
316
|
+
(cookies||{}).inject("") do |string, (name, value)|
|
317
|
+
string << "#{name}=#{value}; "
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def http_session
|
322
|
+
# @http ||= returning(Patron::Session.new) do |session|
|
323
|
+
# session.timeout = 10
|
324
|
+
# session.base_url = "http://localhost:#{SERVER_PORT}"
|
325
|
+
# session.headers['User-Agent'] = 'Nitrous/1.0'
|
326
|
+
# end
|
327
|
+
uri = URI.parse("http://localhost:#{SERVER_PORT}/") unless @http
|
328
|
+
@http ||= Net::HTTP.start(uri.host, uri.port)
|
329
|
+
end
|
330
|
+
|
331
|
+
def parse_result
|
332
|
+
@headers = @response.to_hash
|
333
|
+
@cookies ||= {}
|
334
|
+
(@headers['set-cookie'] || [] ).each do |string|
|
335
|
+
name, value = string.match(/^([^=]*)=([^;]*);/)[1,2]
|
336
|
+
@cookies[name] = value
|
337
|
+
end
|
338
|
+
@status, @status_message = @response.code.to_i, @response.message
|
339
|
+
end
|
340
|
+
|
341
|
+
def name_with_prefix(prefix, name)
|
342
|
+
prefix ? "#{prefix}[#{name}]" : name.to_s
|
343
|
+
end
|
344
|
+
|
345
|
+
def requestify(parameters, prefix=nil)
|
346
|
+
if Hash === parameters
|
347
|
+
return nil if parameters.empty?
|
348
|
+
parameters.map { |k,v| requestify(v, name_with_prefix(prefix, k)) }.join("&")
|
349
|
+
elsif Array === parameters
|
350
|
+
parameters.map { |v| requestify(v, name_with_prefix(prefix, "")) }.join("&")
|
351
|
+
elsif prefix.nil?
|
352
|
+
parameters
|
353
|
+
else
|
354
|
+
"#{CGI.escape(prefix)}=#{CGI.escape(parameters.to_s)}"
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
class DummyFile
|
359
|
+
def initialize(name, content)
|
360
|
+
@name, @content = name, content
|
361
|
+
end
|
362
|
+
|
363
|
+
def read
|
364
|
+
@content
|
365
|
+
end
|
366
|
+
|
367
|
+
def path
|
368
|
+
"/tmp/#{@name}"
|
369
|
+
end
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|