akephalos-nerian 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT_LICENSE +20 -0
- data/README.md +56 -0
- data/bin/akephalos +88 -0
- data/lib/akephalos.rb +19 -0
- data/lib/akephalos/capybara.rb +316 -0
- data/lib/akephalos/client.rb +112 -0
- data/lib/akephalos/client/cookies.rb +73 -0
- data/lib/akephalos/client/filter.rb +120 -0
- data/lib/akephalos/configuration.rb +49 -0
- data/lib/akephalos/console.rb +32 -0
- data/lib/akephalos/cucumber.rb +6 -0
- data/lib/akephalos/htmlunit.rb +38 -0
- data/lib/akephalos/htmlunit/ext/http_method.rb +30 -0
- data/lib/akephalos/node.rb +172 -0
- data/lib/akephalos/page.rb +113 -0
- data/lib/akephalos/remote_client.rb +84 -0
- data/lib/akephalos/server.rb +56 -0
- data/lib/akephalos/version.rb +3 -0
- data/src/htmlunit/apache-mime4j-0.6.jar +0 -0
- data/src/htmlunit/commons-codec-1.4.jar +0 -0
- data/src/htmlunit/commons-collections-3.2.1.jar +0 -0
- data/src/htmlunit/commons-io-1.4.jar +0 -0
- data/src/htmlunit/commons-lang-2.4.jar +0 -0
- data/src/htmlunit/commons-logging-1.1.1.jar +0 -0
- data/src/htmlunit/cssparser-0.9.5.jar +0 -0
- data/src/htmlunit/htmlunit-2.8.jar +0 -0
- data/src/htmlunit/htmlunit-core-js-2.8.jar +0 -0
- data/src/htmlunit/httpclient-4.0.1.jar +0 -0
- data/src/htmlunit/httpcore-4.0.1.jar +0 -0
- data/src/htmlunit/httpmime-4.0.1.jar +0 -0
- data/src/htmlunit/nekohtml-1.9.14.jar +0 -0
- data/src/htmlunit/sac-1.3.jar +0 -0
- data/src/htmlunit/serializer-2.7.1.jar +0 -0
- data/src/htmlunit/xalan-2.7.1.jar +0 -0
- data/src/htmlunit/xercesImpl-2.9.1.jar +0 -0
- data/src/htmlunit/xml-apis-1.3.04.jar +0 -0
- metadata +164 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'akephalos/configuration'
|
2
|
+
|
3
|
+
if RUBY_PLATFORM != "java"
|
4
|
+
require 'akephalos/remote_client'
|
5
|
+
Akephalos::Client = Akephalos::RemoteClient
|
6
|
+
else
|
7
|
+
require 'akephalos/htmlunit'
|
8
|
+
require 'akephalos/htmlunit/ext/http_method'
|
9
|
+
|
10
|
+
require 'akephalos/page'
|
11
|
+
require 'akephalos/node'
|
12
|
+
|
13
|
+
require 'akephalos/client/cookies'
|
14
|
+
require 'akephalos/client/filter'
|
15
|
+
|
16
|
+
module Akephalos
|
17
|
+
|
18
|
+
# Akephalos::Client wraps HtmlUnit's WebClient class. It is the main entry
|
19
|
+
# point for all interaction with the browser, exposing its current page and
|
20
|
+
# allowing navigation.
|
21
|
+
class Client
|
22
|
+
attr_reader :page
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@_client = java.util.concurrent.FutureTask.new do
|
26
|
+
client = HtmlUnit::WebClient.new
|
27
|
+
|
28
|
+
Filter.new(client)
|
29
|
+
client.setThrowExceptionOnFailingStatusCode(false)
|
30
|
+
client.setAjaxController(HtmlUnit::NicelyResynchronizingAjaxController.new)
|
31
|
+
client.setCssErrorHandler(HtmlUnit::SilentCssErrorHandler.new)
|
32
|
+
client.setThrowExceptionOnScriptError(false);
|
33
|
+
client
|
34
|
+
end
|
35
|
+
Thread.new { @_client.run }
|
36
|
+
end
|
37
|
+
|
38
|
+
# Set the global configuration settings for Akephalos.
|
39
|
+
#
|
40
|
+
# @note This is only used when communicating over DRb, since just a
|
41
|
+
# single client instance is exposed.
|
42
|
+
# @param [Hash] config the configuration settings
|
43
|
+
# @return [Hash] the configuration
|
44
|
+
def configuration=(config)
|
45
|
+
Akephalos.configuration = config
|
46
|
+
end
|
47
|
+
|
48
|
+
# Visit the requested URL and return the page.
|
49
|
+
#
|
50
|
+
# @param [String] url the URL to load
|
51
|
+
# @return [Page] the loaded page
|
52
|
+
def visit(url)
|
53
|
+
client.getPage(url)
|
54
|
+
page
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [Cookies] the cookies for this session
|
58
|
+
def cookies
|
59
|
+
@cookies ||= Cookies.new(client.getCookieManager)
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [String] the current user agent string
|
63
|
+
def user_agent
|
64
|
+
@user_agent || client.getBrowserVersion.getUserAgent
|
65
|
+
end
|
66
|
+
|
67
|
+
# Set the User-Agent header for this session. If :default is given, the
|
68
|
+
# User-Agent header will be reset to the default browser's user agent.
|
69
|
+
#
|
70
|
+
# @param [:default] user_agent the default user agent
|
71
|
+
# @param [String] user_agent the user agent string to use
|
72
|
+
def user_agent=(user_agent)
|
73
|
+
if user_agent == :default
|
74
|
+
@user_agent = nil
|
75
|
+
client.removeRequestHeader("User-Agent")
|
76
|
+
else
|
77
|
+
@user_agent = user_agent
|
78
|
+
client.addRequestHeader("User-Agent", user_agent)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [Page] the current page
|
83
|
+
def page
|
84
|
+
self.page = client.getCurrentWindow.getTopWindow.getEnclosedPage
|
85
|
+
@page
|
86
|
+
end
|
87
|
+
|
88
|
+
# Update the current page.
|
89
|
+
#
|
90
|
+
# @param [HtmlUnit::HtmlPage] _page the new page
|
91
|
+
# @return [Page] the new page
|
92
|
+
def page=(_page)
|
93
|
+
if @page != _page
|
94
|
+
@page = Page.new(_page)
|
95
|
+
end
|
96
|
+
@page
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
# Call the future set up in #initialize and return the WebCLient
|
102
|
+
# instance.
|
103
|
+
#
|
104
|
+
# @return [HtmlUnit::WebClient] the WebClient instance
|
105
|
+
def client
|
106
|
+
@client ||= @_client.get.tap do |client|
|
107
|
+
client.getCurrentWindow.getHistory.ignoreNewPages_.set(true)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Akephalos
|
2
|
+
class Client
|
3
|
+
# Interface for working with HtmlUnit's CookieManager, providing a basic
|
4
|
+
# API for manipulating the cookies in a session.
|
5
|
+
class Cookies
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
# @param [HtmlUnit::CookieManager] cookie manager
|
9
|
+
def initialize(cookie_manager)
|
10
|
+
@cookie_manager = cookie_manager
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param [name] the cookie name
|
14
|
+
# @return [Cookie] the cookie with the given name
|
15
|
+
# @return [nil] when no cookie is found
|
16
|
+
def [](name)
|
17
|
+
cookie = @cookie_manager.getCookie(name)
|
18
|
+
Cookie.new(cookie) if cookie
|
19
|
+
end
|
20
|
+
|
21
|
+
# Clears all cookies for this session.
|
22
|
+
def clear
|
23
|
+
@cookie_manager.clearCookies
|
24
|
+
end
|
25
|
+
|
26
|
+
# Iterator for all cookies in the current session.
|
27
|
+
def each
|
28
|
+
@cookie_manager.getCookies.each do |cookie|
|
29
|
+
yield Cookie.new(cookie)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Remove the cookie from the session.
|
34
|
+
#
|
35
|
+
# @param [Cookie] the cookie to remove
|
36
|
+
def delete(cookie)
|
37
|
+
@cookie_manager.removeCookie(cookie.native)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [true, false] whether there are any cookies
|
41
|
+
def empty?
|
42
|
+
!any?
|
43
|
+
end
|
44
|
+
|
45
|
+
class Cookie
|
46
|
+
|
47
|
+
attr_reader :domain, :expires, :name, :path, :value
|
48
|
+
|
49
|
+
# @param [HtmlUnit::Cookie] the cookie
|
50
|
+
def initialize(cookie)
|
51
|
+
@_cookie = cookie
|
52
|
+
@domain = cookie.getDomain
|
53
|
+
@expires = cookie.getExpires
|
54
|
+
@name = cookie.getName
|
55
|
+
@path = cookie.getPath
|
56
|
+
@value = cookie.getValue
|
57
|
+
@secure = cookie.isSecure
|
58
|
+
end
|
59
|
+
|
60
|
+
def secure?
|
61
|
+
!!@secure
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [HtmlUnit::Cookie] the native cookie object
|
65
|
+
# @api private
|
66
|
+
def native
|
67
|
+
@_cookie
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Akephalos
|
2
|
+
class Client
|
3
|
+
|
4
|
+
# Akephalos::Client::Filter extends HtmlUnit's WebConnectionWrapper to
|
5
|
+
# enable filtering outgoing requests generated interally by HtmlUnit.
|
6
|
+
#
|
7
|
+
# When a request comes through, it will be tested against the filters
|
8
|
+
# defined in Akephalos.filters and return a mock response if a match is
|
9
|
+
# found. If no filters are defined, or no filters match the request, then
|
10
|
+
# the response will bubble up to HtmlUnit for the normal request/response
|
11
|
+
# cycle.
|
12
|
+
class Filter < HtmlUnit::Util::WebConnectionWrapper
|
13
|
+
# Filters an outgoing request, and if a match is found, returns the mock
|
14
|
+
# response.
|
15
|
+
#
|
16
|
+
# @param [WebRequest] request the pending HTTP request
|
17
|
+
# @return [WebResponseImpl] when the request matches a defined filter
|
18
|
+
# @return [nil] when no filters match the request
|
19
|
+
def filter(request)
|
20
|
+
if filter = find_filter(request)
|
21
|
+
start_time = Time.now
|
22
|
+
headers = filter[:headers].map do |name, value|
|
23
|
+
HtmlUnit::Util::NameValuePair.new(name.to_s, value.to_s)
|
24
|
+
end
|
25
|
+
response = HtmlUnit::WebResponseData.new(
|
26
|
+
filter[:body].to_s.to_java_bytes,
|
27
|
+
filter[:status],
|
28
|
+
HTTP_STATUS_CODES.fetch(filter[:status], "Unknown"),
|
29
|
+
headers
|
30
|
+
)
|
31
|
+
HtmlUnit::WebResponseImpl.new(response, request, Time.now - start_time)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Searches for a filter which matches the request's HTTP method and url.
|
36
|
+
#
|
37
|
+
# @param [WebRequest] request the pending HTTP request
|
38
|
+
# @return [Hash] when a filter matches the request
|
39
|
+
# @return [nil] when no filters match the request
|
40
|
+
def find_filter(request)
|
41
|
+
Akephalos.filters.find do |filter|
|
42
|
+
request.http_method === filter[:method] && request.url.to_s =~ filter[:filter]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# This method is called by WebClient when a page is requested, and will
|
47
|
+
# return a mock response if the request matches a defined filter or else
|
48
|
+
# return the actual response.
|
49
|
+
#
|
50
|
+
# @api htmlunit
|
51
|
+
# @param [WebRequest] request the pending HTTP request
|
52
|
+
# @return [WebResponseImpl]
|
53
|
+
def getResponse(request)
|
54
|
+
filter(request) || super
|
55
|
+
end
|
56
|
+
|
57
|
+
# Map of status codes to their English descriptions.
|
58
|
+
HTTP_STATUS_CODES = {
|
59
|
+
100 => "Continue",
|
60
|
+
101 => "Switching Protocols",
|
61
|
+
102 => "Processing",
|
62
|
+
200 => "OK",
|
63
|
+
201 => "Created",
|
64
|
+
202 => "Accepted",
|
65
|
+
203 => "Non-Authoritative Information",
|
66
|
+
204 => "No Content",
|
67
|
+
205 => "Reset Content",
|
68
|
+
206 => "Partial Content",
|
69
|
+
207 => "Multi-Status",
|
70
|
+
300 => "Multiple Choices",
|
71
|
+
301 => "Moved Permanently",
|
72
|
+
302 => "Found",
|
73
|
+
303 => "See Other",
|
74
|
+
304 => "Not Modified",
|
75
|
+
305 => "Use Proxy",
|
76
|
+
306 => "Switch Proxy",
|
77
|
+
307 => "Temporary Redirect",
|
78
|
+
400 => "Bad Request",
|
79
|
+
401 => "Unauthorized",
|
80
|
+
402 => "Payment Required",
|
81
|
+
403 => "Forbidden",
|
82
|
+
404 => "Not Found",
|
83
|
+
405 => "Method Not Allowed",
|
84
|
+
406 => "Not Acceptable",
|
85
|
+
407 => "Proxy Authentication Required",
|
86
|
+
408 => "Request Timeout",
|
87
|
+
409 => "Conflict",
|
88
|
+
410 => "Gone",
|
89
|
+
411 => "Length Required",
|
90
|
+
412 => "Precondition Failed",
|
91
|
+
413 => "Request Entity Too Large",
|
92
|
+
414 => "Request-URI Too Long",
|
93
|
+
415 => "Unsupported Media Type",
|
94
|
+
416 => "Requested Range Not Satisfiable",
|
95
|
+
417 => "Expectation Failed",
|
96
|
+
418 => "I'm a teapot",
|
97
|
+
421 => "There are too many connections from your internet address",
|
98
|
+
422 => "Unprocessable Entity",
|
99
|
+
423 => "Locked",
|
100
|
+
424 => "Failed Dependency",
|
101
|
+
425 => "Unordered Collection",
|
102
|
+
426 => "Upgrade Required",
|
103
|
+
449 => "Retry With",
|
104
|
+
450 => "Blocked by Windows Parental Controls",
|
105
|
+
500 => "Internal Server Error",
|
106
|
+
501 => "Not Implemented",
|
107
|
+
502 => "Bad Gateway",
|
108
|
+
503 => "Service Unavailable",
|
109
|
+
504 => "Gateway Timeout",
|
110
|
+
505 => "HTTP Version Not Supported",
|
111
|
+
506 => "Variant Also Negotiates",
|
112
|
+
507 => "Insufficient Storage",
|
113
|
+
509 => "Bandwidth Limit Exceeded",
|
114
|
+
510 => "Not Extended",
|
115
|
+
530 => "User access denied"
|
116
|
+
}.freeze
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Akephalos
|
2
|
+
|
3
|
+
@configuration = {}
|
4
|
+
|
5
|
+
class << self
|
6
|
+
# @return [Hash] the configuration
|
7
|
+
attr_accessor :configuration
|
8
|
+
end
|
9
|
+
|
10
|
+
module Filters
|
11
|
+
# @return [Array] all defined filters
|
12
|
+
def filters
|
13
|
+
configuration[:filters] ||= []
|
14
|
+
end
|
15
|
+
|
16
|
+
# Defines a new filter to be tested by Akephalos::Filter when executing
|
17
|
+
# page requests. An HTTP method and a regex or string to match against the
|
18
|
+
# URL are required for defining a filter.
|
19
|
+
#
|
20
|
+
# You can additionally pass the following options to define how the
|
21
|
+
# filtered request should respond:
|
22
|
+
#
|
23
|
+
# :status (defaults to 200)
|
24
|
+
# :body (defaults to "")
|
25
|
+
# :headers (defaults to {})
|
26
|
+
#
|
27
|
+
# If we define a filter with no additional options, then, we will get an
|
28
|
+
# empty HTML response:
|
29
|
+
#
|
30
|
+
# Akephalos.filter :post, "http://example.com"
|
31
|
+
# Akephalos.filter :any, %r{http://.*\.com}
|
32
|
+
#
|
33
|
+
# If you instead, say, wanted to simulate a failure in an external system,
|
34
|
+
# you could do this:
|
35
|
+
#
|
36
|
+
# Akephalos.filter :post, "http://example.com",
|
37
|
+
# :status => 500, :body => "Something went wrong"
|
38
|
+
#
|
39
|
+
# @param [Symbol] method the HTTP method to match
|
40
|
+
# @param [RegExp, String] regex URL matcher
|
41
|
+
# @param [Hash] options response values
|
42
|
+
def filter(method, regex, options = {})
|
43
|
+
regex = Regexp.new(Regexp.escape(regex)) if regex.is_a?(String)
|
44
|
+
filters << {:method => method, :filter => regex, :status => 200, :body => "", :headers => {}}.merge!(options)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
extend Filters
|
49
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Begin a new Capybara session, by default connecting to localhost on port
|
2
|
+
# 3000.
|
3
|
+
def session
|
4
|
+
Capybara.app_host ||= "http://localhost:3000"
|
5
|
+
@session ||= Capybara::Session.new(:Akephalos)
|
6
|
+
end
|
7
|
+
alias page session
|
8
|
+
|
9
|
+
module Akephalos
|
10
|
+
# Simple class for starting an IRB session.
|
11
|
+
class Console
|
12
|
+
|
13
|
+
# Start an IRB session. Tries to load irb/completion, and also loads a
|
14
|
+
# .irbrc file if it exists.
|
15
|
+
def self.start
|
16
|
+
require 'irb'
|
17
|
+
|
18
|
+
begin
|
19
|
+
require 'irb/completion'
|
20
|
+
rescue Exception
|
21
|
+
# No readline available, proceed anyway.
|
22
|
+
end
|
23
|
+
|
24
|
+
if ::File.exists? ".irbrc"
|
25
|
+
ENV['IRBRC'] = ".irbrc"
|
26
|
+
end
|
27
|
+
|
28
|
+
IRB.start
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "pathname"
|
2
|
+
require "java"
|
3
|
+
|
4
|
+
dependency_directory = $:.detect { |path| Dir[File.join(path, 'htmlunit/htmlunit-*.jar')].any? }
|
5
|
+
|
6
|
+
raise "Could not find htmlunit/htmlunit-VERSION.jar in load path:\n [ #{$:.join(",\n ")}\n ]" unless dependency_directory
|
7
|
+
|
8
|
+
Dir[File.join(dependency_directory, "htmlunit/*.jar")].each do |jar|
|
9
|
+
require jar
|
10
|
+
end
|
11
|
+
|
12
|
+
java.lang.System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog")
|
13
|
+
java.lang.System.setProperty("org.apache.commons.logging.simplelog.defaultlog", "fatal")
|
14
|
+
java.lang.System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true")
|
15
|
+
|
16
|
+
# Container module for com.gargoylesoftware.htmlunit namespace.
|
17
|
+
module HtmlUnit
|
18
|
+
java_import "com.gargoylesoftware.htmlunit.BrowserVersion"
|
19
|
+
java_import "com.gargoylesoftware.htmlunit.History"
|
20
|
+
java_import "com.gargoylesoftware.htmlunit.HttpMethod"
|
21
|
+
java_import "com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController"
|
22
|
+
java_import "com.gargoylesoftware.htmlunit.SilentCssErrorHandler"
|
23
|
+
java_import "com.gargoylesoftware.htmlunit.WebClient"
|
24
|
+
java_import "com.gargoylesoftware.htmlunit.WebResponseData"
|
25
|
+
java_import "com.gargoylesoftware.htmlunit.WebResponseImpl"
|
26
|
+
|
27
|
+
# Container module for com.gargoylesoftware.htmlunit.util namespace.
|
28
|
+
module Util
|
29
|
+
java_import "com.gargoylesoftware.htmlunit.util.NameValuePair"
|
30
|
+
java_import "com.gargoylesoftware.htmlunit.util.WebConnectionWrapper"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Disable history tracking
|
34
|
+
History.field_reader :ignoreNewPages_
|
35
|
+
|
36
|
+
# Run in Firefox compatibility mode
|
37
|
+
BrowserVersion.setDefault(BrowserVersion::FIREFOX_3)
|
38
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module HtmlUnit
|
2
|
+
# Reopen HtmlUnit's HttpMethod class to add convenience methods.
|
3
|
+
class HttpMethod
|
4
|
+
|
5
|
+
# Loosely compare HttpMethod with another object, accepting either an
|
6
|
+
# HttpMethod instance or a symbol describing the method. Note that :any is a
|
7
|
+
# special symbol which will always return true.
|
8
|
+
#
|
9
|
+
# @param [HttpMethod] other an HtmlUnit HttpMethod object
|
10
|
+
# @param [Symbol] other a symbolized representation of an http method
|
11
|
+
# @return [true/false]
|
12
|
+
def ===(other)
|
13
|
+
case other
|
14
|
+
when HttpMethod
|
15
|
+
super
|
16
|
+
when :any
|
17
|
+
true
|
18
|
+
when :get
|
19
|
+
self == self.class::GET
|
20
|
+
when :post
|
21
|
+
self == self.class::POST
|
22
|
+
when :put
|
23
|
+
self == self.class::PUT
|
24
|
+
when :delete
|
25
|
+
self == self.class::DELETE
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|