nitrous 1.0.3
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.
- 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
|