jakewendt-html_test 0.2.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.
@@ -0,0 +1,57 @@
1
+ module Html
2
+ module Test
3
+ module UrlSelector
4
+ def skip_url?(url, root_url = nil)
5
+ if url.blank? || unsupported_protocol?(url) || special_url?(url)
6
+ true
7
+ else
8
+ false
9
+ end
10
+ end
11
+
12
+ def special_url?(url)
13
+ [/^javascript:/, /^mailto:/, /^\#$/].any? { |pattern| url =~ pattern }
14
+ end
15
+
16
+ def external_http?(url, root_url = nil)
17
+ if root_url
18
+ http_protocol?(url) && !url.starts_with?(root_url)
19
+ else
20
+ http_protocol?(url)
21
+ end
22
+ end
23
+
24
+ def http_protocol?(url)
25
+ url =~ %r{^http(?:s)?://} ? true : false
26
+ end
27
+
28
+ def has_protocol?(url)
29
+ url =~ %r{^[a-z]+://} ? true : false
30
+ end
31
+
32
+ def unsupported_protocol?(url)
33
+ has_protocol?(url) && !http_protocol?(url)
34
+ end
35
+
36
+ def anchor_urls
37
+ select("a").map { |l| l.attributes['href'] }
38
+ end
39
+
40
+ def image_urls
41
+ select("img").map { |i| i.attributes['src'] }
42
+ end
43
+
44
+ def form_urls
45
+ select("form").map { |i| i.attributes['action'] }
46
+ end
47
+
48
+ def response_body
49
+ self.respond_to?(:response) ? response.body : @response.body
50
+ end
51
+
52
+ def select(pattern)
53
+ HTML::Selector.new(pattern).select(HTML::Document.new(response_body).root)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,55 @@
1
+ module Html
2
+ module Test
3
+ class ValidateFilter
4
+ attr_accessor :request, :response, :params, :validators
5
+
6
+ include ::Test::Unit::Assertions
7
+ include ::Html::Test::Assertions
8
+
9
+ def initialize(controller)
10
+ self.request = controller.request
11
+ self.response = controller.response
12
+ self.params = controller.params
13
+ self.validators = controller.class.validators
14
+ end
15
+
16
+ def validate_page
17
+ url = request.request_uri
18
+ return if (!should_validate? || ValidateFilter.already_validated?(url))
19
+ # assert_validates(validators, response.body.strip, url, :verbose => true)
20
+ assert_validates(validators, response.body.strip, url )
21
+ ValidateFilter.mark_url_validated(url)
22
+ end
23
+
24
+ def self.already_validated?(url)
25
+ if Html::Test::Validator.revalidate_all
26
+ false
27
+ else
28
+ validated_urls[url]
29
+ end
30
+ end
31
+
32
+ def self.mark_url_validated(url)
33
+ validated_urls[url] = true
34
+ end
35
+
36
+ def self.validated_urls
37
+ @validated_urls ||= {}
38
+ end
39
+
40
+ # Override this method if you only want to validate a subset of pages
41
+ def should_validate?
42
+ response.status =~ /200/ &&
43
+ (response.headers['Content-Type'] =~ /text\/html/i || response.body =~ /<html/)
44
+ end
45
+
46
+ # Used in testing (of html_test_extension plugin)
47
+ # to remove the validated_urls hash
48
+ # so can test with the same url.
49
+ def self.clear_validated_urls
50
+ @validated_urls = {}
51
+ end
52
+
53
+ end
54
+ end
55
+ end
data/lib/validator.rb ADDED
@@ -0,0 +1,121 @@
1
+ require 'tempfile'
2
+ require 'net/http'
3
+ require 'fileutils'
4
+
5
+ module Html
6
+ module Test
7
+ class Validator
8
+
9
+ # verbose = true shows "validating ..."
10
+ # verbose = false shows NOTHING
11
+ @@verbose = true
12
+ cattr_accessor :verbose
13
+ #
14
+ # revalidate_all = true will validate every call to a url
15
+ # revalidate_all = false will only validate the first call to a url
16
+ @@revalidate_all = true
17
+ cattr_accessor :revalidate_all
18
+
19
+ @@tidy_ignore_list = []
20
+ cattr_accessor :tidy_ignore_list
21
+
22
+ # For local validation you might change this to http://localhost/validator/htdocs/check
23
+ DEFAULT_W3C_URL = "http://validator.w3.org/check"
24
+ @@w3c_url = DEFAULT_W3C_URL
25
+ cattr_accessor :w3c_url
26
+
27
+ # Whether the W3C validator should show the HTML document being validated in
28
+ # its response. Set to 0 to disable.
29
+ @@w3c_show_source = "1"
30
+ cattr_accessor :w3c_show_source
31
+
32
+ DEFAULT_DTD = File.join(File.dirname(__FILE__), 'DTD', 'xhtml1-strict.dtd')
33
+
34
+ # Path to DTD file that the xmllint validator uses
35
+ def self.dtd(document)
36
+ DEFAULT_DTD
37
+ end
38
+
39
+ # Validate an HTML document string using tidy.
40
+ # Code excerpted from the rails_tidy plugin
41
+ def self.tidy_errors(body)
42
+ tidy = RailsTidy.tidy_factory
43
+ tidy.clean(body)
44
+ errors = tidy.errors.empty? ? nil :
45
+ tidy.errors.delete_if { |e| tidy_ignore_list.select { |p| e =~ p }.size > 0 }.join("\n")
46
+ tidy.release
47
+ errors.blank? ? nil : errors
48
+ end
49
+
50
+ # Validate an HTML document string by going to the online W3C validator.
51
+ # Credit for the original code goes to Scott Baron (htonl)
52
+ def self.w3c_errors(body)
53
+ response = Net::HTTP.post_form(URI.parse(w3c_url),
54
+ {'ss'=>w3c_show_source, 'fragment'=>body})
55
+ status = response['x-w3c-validator-status']
56
+ if status != 'Valid'
57
+ # Reference in the stylesheets
58
+ response.body.sub!(%r{@import "./base.css"}, %Q{@import "#{File.dirname(w3c_url)}/base.css"})
59
+ response_file = find_unique_path(File.join(tmp_dir, "w3c_response.html"))
60
+ open(response_file, "w") { |f| f.puts(response.body) }
61
+ "W3C status #{status}. Response from W3C was written to the file #{response_file}"
62
+ else
63
+ nil
64
+ end
65
+ end
66
+
67
+ # Validate an HTML document string using the xmllint command line validator tool.
68
+ # Returns nil if validation passes and an error message otherwise.
69
+ # Original code taken from the book "Enterprise Integration with Ruby"
70
+ def self.xmllint_errors(body)
71
+ error_file = create_tmp_file("xmllint_error")
72
+ doc_file = command = nil
73
+ if dtd(body) =~ /^doctype$/i
74
+ # Use the DOCTYPE declaration
75
+ doc_file = create_tmp_file("xmllint", body)
76
+ command = "xmllint --noout --valid #{doc_file} &> #{error_file}"
77
+ else
78
+ # Override the DOCTYPE declaration
79
+ doc_file = create_tmp_file("xmllint", body.sub(/<!DOCTYPE[^>]+>/m, ""))
80
+ command = "xmllint --noout --dtdvalid #{dtd(body)} #{doc_file} &> #{error_file}"
81
+ end
82
+ system(command)
83
+ status = $?.exitstatus
84
+ if status == 0
85
+ return nil
86
+ else
87
+ failure_doc = File.join(tmp_dir, "xmllint_last_response.html")
88
+ FileUtils.cp doc_file, failure_doc
89
+ return ("command='#{command}'. HTML document at '#{failure_doc}'. " +
90
+ IO.read(error_file))
91
+ end
92
+ end
93
+
94
+ private
95
+ def self.find_unique_path(path)
96
+ filename = File.basename(path)
97
+ ext = File.extname(filename)
98
+ size_no_ext = filename.size - ext.size
99
+ filename_no_ext = filename[0, size_no_ext]
100
+ counter = 2
101
+ while File.exists?(path)
102
+ new_filename = [filename_no_ext, "-", counter, ext].join
103
+ path = File.join(File.dirname(path), new_filename)
104
+ counter += 1
105
+ end
106
+ path
107
+ end
108
+
109
+ def self.create_tmp_file(name, contents = "")
110
+ tmp_file = Tempfile.new(name)
111
+ tmp_file.puts(contents)
112
+ tmp_file.close
113
+ tmp_file.path
114
+ end
115
+
116
+ def self.tmp_dir
117
+ Dir::tmpdir
118
+ end
119
+ end
120
+ end
121
+ end
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'jakewendt-html_test' if RAILS_ENV == 'test'
data/script/validate ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+ # == Synopsis
3
+ # This script validates an HTML page via HTTP and checks for broken
4
+ # links and images. Links are followed one step away from the start page.
5
+ # HTML validation is also done for pages linked internally from the start page
6
+ # (i.e. relative URLs).
7
+ #
8
+ # == Usage
9
+ # vendor/plugins/html_test/script/validate http://my.url.com [options]
10
+ #
11
+ # Options:
12
+ #
13
+ # --no-follow:: Don't follow any anchor or images URLs on
14
+ # the start page (i.e. only one page is requested).
15
+ #
16
+ # --validators validator_list:: A comma separated list of validators to use
17
+ # for HTML validation. Supported validators:
18
+ # tidy, xmllint, w3c. Default validator is
19
+ # tidy if it is installed, otherwise w3c.
20
+ #
21
+ # --dtd dtd_path:: Path to the DTD file to use for xmllint
22
+ # validation. By default xmllint will use
23
+ # the XHTML 1.0 strict DTD. Set dtd_path
24
+ # to "doctype" to make xmllint use the
25
+ # DTD specified in the DOCTYPE tag
26
+ # (can be slow).
27
+ #
28
+ # --skip skip_patterns:: A comma separated list of regexp patterns for
29
+ # URLs to not visit. Using the pattern '.*'
30
+ # is equivalent to the --no-follow option.
31
+ #
32
+ # --only only_pattern:: Only visit URLs matching given regexp pattern
33
+ #
34
+ # --no-external:: Do not visit external URLs, i.e. URLs with
35
+ # different domain than the start page.
36
+ #
37
+ # --quiet:: Don't output anything unless there is a failure.
38
+
39
+ require 'optparse'
40
+ require 'rdoc/usage'
41
+ require 'uri'
42
+
43
+ require File.join(File.dirname(__FILE__), "..", "..", "..", "..", "test", "test_helper")
44
+ require File.join(File.dirname(__FILE__), "..", "lib", "html_test")
45
+
46
+ options = Html::Test::LinkValidator.parse_command_line(ARGV) rescue RDoc::usage
47
+ Html::Test::LinkValidator.new(ARGV[0], options)
@@ -0,0 +1,120 @@
1
+ require File.join(File.dirname(__FILE__), "..", "..", "..", "..", "test", "test_helper")
2
+ require File.join(File.dirname(__FILE__), "test_helper")
3
+
4
+ ActionController::Routing::Routes.draw do |map|
5
+ map.connect 'test/:action', :controller => 'test'
6
+ end
7
+
8
+ class Html::Test::ControllerTest < ActionController::TestCase
9
+ def setup
10
+ @controller = TestController.new
11
+ @request = ActionController::TestRequest.new
12
+ @response = ActionController::TestResponse.new
13
+
14
+ ActionController::Base.validate_all = false
15
+ ActionController::Base.check_urls = true
16
+ ActionController::Base.check_redirects = true
17
+ Html::Test::Validator.tidy_ignore_list = []
18
+ Html::Test::UrlChecker.any_instance.stubs(:rails_public_path).returns(File.join(File.dirname(__FILE__), "public"))
19
+ end
20
+
21
+ def test_assert_validates_success
22
+ get :valid
23
+ assert_response :success
24
+ assert_validates # Should validate tidy, w3c, and xmllint
25
+ end
26
+
27
+ def test_assert_tidy_failure
28
+ get :untidy
29
+ assert_response :success
30
+ assert_raise(Test::Unit::AssertionFailedError) do
31
+ assert_tidy
32
+ end
33
+ assert_w3c
34
+ assert_xmllint
35
+ end
36
+
37
+ def test_assert_w3c_failure
38
+ get :invalid
39
+ assert_response :success
40
+ assert_raise(Test::Unit::AssertionFailedError) do
41
+ assert_w3c
42
+ end
43
+ assert_raise(Test::Unit::AssertionFailedError) do
44
+ assert_xmllint
45
+ end
46
+ assert_tidy
47
+ end
48
+
49
+ def test_url_no_route
50
+ assert_raise(Html::Test::InvalidUrl) do
51
+ get :url_no_route
52
+ end
53
+ end
54
+
55
+ def test_url_no_action
56
+ assert_raise(Html::Test::InvalidUrl) do
57
+ get :url_no_action
58
+ end
59
+ end
60
+
61
+ # TODO: figure out why those tests don't work
62
+ # def test_url_rhtml_template_exists
63
+ # get :rhtml_template
64
+ # end
65
+
66
+ # def test_url_rxml_template_exists
67
+ # get :rxml_template
68
+ # end
69
+
70
+ def test_url_action_no_template
71
+ get :action_no_template
72
+ end
73
+
74
+ def test_redirect_no_action
75
+ assert_raise(Html::Test::InvalidUrl) do
76
+ get :redirect_no_action
77
+ end
78
+ end
79
+
80
+ def test_redirect_valid_action
81
+ get :redirect_valid_action
82
+ assert_response :redirect
83
+ end
84
+
85
+ def test_redirect_external
86
+ get :redirect_external
87
+ assert_response :redirect
88
+ end
89
+
90
+ def test_redirect_valid_with_host
91
+ get :redirect_valid_with_host
92
+ assert_response :redirect
93
+ end
94
+
95
+ def test_image_file_exists
96
+ get :image_file_exists
97
+ assert_response :success
98
+ end
99
+
100
+ def test_image_does_not_exist
101
+ assert_raise(Html::Test::InvalidUrl) do
102
+ get :image_file_does_not_exist
103
+ end
104
+ end
105
+
106
+ def test_urls_to_resolve
107
+ checker = Html::Test::UrlChecker.new(@controller)
108
+ checker.stubs(:response_body).returns(<<-HTML
109
+ <a href="anchor_url">hej</a>
110
+ Some text and <div>markup</div>
111
+ <img src="image_url"/>
112
+ Some more text
113
+ <form action="form_url">
114
+ Some text
115
+ </form>
116
+ HTML
117
+ )
118
+ assert_equal(%w(anchor_url form_url image_url), checker.send(:urls_to_check).sort)
119
+ end
120
+ end
@@ -0,0 +1,73 @@
1
+ require File.join(File.dirname(__FILE__), "..", "..", "..", "..", "test", "test_helper")
2
+ require File.join(File.dirname(__FILE__), "test_helper")
3
+
4
+ ActionController::Routing::Routes.draw do |map|
5
+ map.connect 'test/:action', :controller => 'test'
6
+ end
7
+ ApplicationController.validate_all = false
8
+ ApplicationController.check_urls = true
9
+ ApplicationController.check_redirects = true
10
+
11
+ class Html::Test::IntegrationTest < ActionController::IntegrationTest
12
+ def test_assert_valides_invokes_all
13
+ get('/test/valid')
14
+ assert_response :success
15
+
16
+ [:tidy_errors, :w3c_errors, :xmllint_errors].each do |method|
17
+ Html::Test::Validator.expects(method).with(@response.body)
18
+ end
19
+ assert_validates
20
+ end
21
+
22
+ def test_assert_tidy_invoked
23
+ get('/test/valid')
24
+ assert_response :success
25
+ Html::Test::Validator.expects(:tidy_errors).with(@response.body)
26
+ assert_tidy
27
+ end
28
+
29
+ def test_assert_valid_success
30
+ get('/test/valid')
31
+ assert_response :success
32
+ assert_validates
33
+ end
34
+
35
+ def test_assert_tidy_failure
36
+ file_string = TestController.test_file_string(:untidy)
37
+ assert_raise(Test::Unit::AssertionFailedError) do
38
+ assert_tidy(file_string)
39
+ end
40
+ assert_w3c(file_string)
41
+ assert_xmllint(file_string)
42
+ end
43
+
44
+ def test_assert_w3c_failure
45
+ file_string = TestController.test_file_string(:invalid)
46
+ assert_raise(Test::Unit::AssertionFailedError) do
47
+ assert_w3c(file_string)
48
+ end
49
+ assert_raise(Test::Unit::AssertionFailedError) do
50
+ assert_xmllint(file_string)
51
+ end
52
+ assert_raise(Test::Unit::AssertionFailedError) do
53
+ Html::Test::Validator.expects(:dtd).returns("doctype")
54
+ assert_xmllint(file_string)
55
+ end
56
+ assert_tidy(file_string)
57
+ end
58
+
59
+ def test_url_no_route
60
+ TestController.any_instance.expects(:rescue_action).with() { |e| e.class == Html::Test::InvalidUrl }
61
+ get '/test/url_no_route'
62
+ end
63
+
64
+ def test_url_no_route_relative
65
+ TestController.any_instance.expects(:rescue_action).with() { |e| e.class == Html::Test::InvalidUrl }
66
+ get '/test/url_no_route_relative'
67
+ end
68
+
69
+ def test_redirect_valid_action
70
+ get '/test/redirect_valid_action'
71
+ assert_response :redirect
72
+ end
73
+ end
data/test/invalid.html ADDED
@@ -0,0 +1,15 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3
+ <html>
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
6
+ <title>A perfectly invalid document</title>
7
+ </head>
8
+ <body>
9
+ <h1>This is a perfectly invalid XHTML 1 document</h1>
10
+
11
+ Here are some unquoted chars: <>
12
+
13
+ Tidy may not complain about this document, but that's just crazy.
14
+ </body>
15
+ </html>
@@ -0,0 +1,141 @@
1
+ require File.join(File.dirname(__FILE__), "..", "..", "..", "..", "test", "test_helper")
2
+ require File.join(File.dirname(__FILE__), "test_helper")
3
+ require 'ostruct'
4
+
5
+ # Stub out the HTTP requests
6
+ module Net
7
+ class HTTP
8
+ @@test_requests = []
9
+ cattr_accessor :test_requests
10
+
11
+ @@test_with_links = false
12
+ cattr_accessor :test_with_links
13
+
14
+ def self.get_response(uri)
15
+ self.test_requests << uri.to_s
16
+
17
+ if uri.path =~ /valid_links/ or test_with_links
18
+ file = :valid_links
19
+ else
20
+ file = :valid
21
+ end
22
+ test_with_links = false
23
+ return OpenStruct.new({
24
+ :body => TestController.test_file_string(file),
25
+ :code => "200",
26
+ :header => {'content-type' => 'text/html'}
27
+ })
28
+ end
29
+ end
30
+ end
31
+
32
+ class Html::Test::LinkValidatorTest < Test::Unit::TestCase
33
+ def setup
34
+ Net::HTTP.test_requests = []
35
+ end
36
+
37
+ def test_parse_command_line
38
+ # Missing URL
39
+ assert_raise(RuntimeError) { Html::Test::LinkValidator.parse_command_line(["--no-follow"]) }
40
+
41
+ # Missing skip patterns
42
+ options_no_skip = ["http://my.url.com", "--no-follow", "--validators", "w3c,tidy,xmllint",
43
+ "--no-external", "--dtd", "some.dtd", "--only", "some/url", "--skip"]
44
+ assert_raise(OptionParser::MissingArgument) do
45
+ Html::Test::LinkValidator.parse_command_line(options_no_skip.dup)
46
+ end
47
+
48
+ # All options - valid
49
+ assert_equal({
50
+ :follow_links => false,
51
+ :validators => ["w3c", "tidy", "xmllint"],
52
+ :follow_external => false,
53
+ :dtd => "some.dtd",
54
+ :only_pattern => Regexp.new("some/url"),
55
+ :skip_patterns => [/pattern1/, /pattern2/]
56
+ }, Html::Test::LinkValidator.parse_command_line(options_no_skip << "pattern1,pattern2"))
57
+ end
58
+
59
+ def test_default_options
60
+ url = "http://localhost:3000"
61
+ validator = Html::Test::LinkValidator.new(url)
62
+
63
+ assert_equal url, Net::HTTP.test_requests.first
64
+ assert_equal 1, Net::HTTP.test_requests.size
65
+
66
+ assert validator.options[:follow_links]
67
+ assert_equal ['tidy'], validator.options[:validators]
68
+ assert_equal [], validator.options[:skip_patterns]
69
+ assert_nil validator.options[:only_pattern]
70
+ assert validator.options[:follow_external]
71
+ end
72
+
73
+ def test_quiet
74
+ validator = Html::Test::LinkValidator.new("http://my.cool.site", {:quiet => true})
75
+ # Even in quiet mode the log should be populated
76
+ assert_match /my\.cool\.site/, validator.log
77
+ end
78
+
79
+ def test_link_follow_page
80
+ validator = Html::Test::LinkValidator.new("http://site.com/dir/valid_links")
81
+ # All links visited
82
+ assert_equal(["http://site.com/dir/valid_links",
83
+ "http://site.com/link1",
84
+ "http://site.com/dir/link2",
85
+ "http://site.com/dir/foobar/link3",
86
+ "http://foobar.com/external"].sort,
87
+ Net::HTTP.test_requests.sort)
88
+ end
89
+
90
+ def test_link_follow_root
91
+ %w(http://site.com http://site.com/).each do |url|
92
+ Net::HTTP.test_requests = []
93
+ Net::HTTP.test_with_links = true
94
+ validator = Html::Test::LinkValidator.new(url)
95
+ # All links visited
96
+ assert_equal([url,
97
+ "http://site.com/link1",
98
+ "http://site.com/link2",
99
+ "http://site.com/foobar/link3",
100
+ "http://foobar.com/external"].sort,
101
+ Net::HTTP.test_requests.sort)
102
+ end
103
+ end
104
+
105
+ def test_link_follow_dir
106
+ Net::HTTP.test_with_links = true
107
+ validator = Html::Test::LinkValidator.new("http://site.com/dir/")
108
+ # All links visited
109
+ assert_equal(["http://site.com/dir/",
110
+ "http://site.com/link1",
111
+ "http://site.com/dir/link2",
112
+ "http://site.com/dir/foobar/link3",
113
+ "http://foobar.com/external"].sort,
114
+ Net::HTTP.test_requests.sort)
115
+ end
116
+
117
+ def test_skip_patterns
118
+ url = "http://my.cool.site/valid_links"
119
+ validator = Html::Test::LinkValidator.new(url,
120
+ {:skip_patterns => [/link[12]/, /http/]})
121
+ assert_equal [url, "http://my.cool.site/foobar/link3"].sort,
122
+ Net::HTTP.test_requests.sort
123
+ end
124
+
125
+ def test_only_pattern
126
+ url = "http://my.cool.site/dir/valid_links"
127
+ validator = Html::Test::LinkValidator.new(url, {:only_pattern => /link[12]/})
128
+ assert_equal [url, "http://my.cool.site/link1", "http://my.cool.site/dir/link2"].sort,
129
+ Net::HTTP.test_requests.sort
130
+ end
131
+
132
+ def test_follow_external_false
133
+ Net::HTTP.test_with_links = true
134
+ validator = Html::Test::LinkValidator.new("http://site.com/dir/", {:follow_external => false})
135
+ assert_equal(["http://site.com/dir/",
136
+ "http://site.com/link1",
137
+ "http://site.com/dir/link2",
138
+ "http://site.com/dir/foobar/link3"].sort,
139
+ Net::HTTP.test_requests.sort)
140
+ end
141
+ end
File without changes
@@ -0,0 +1 @@
1
+ Some contents
@@ -0,0 +1,2 @@
1
+ xml.foo do
2
+ end