ape 1.0.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -1
- data/README +22 -8
- data/Rakefile +66 -0
- data/bin/ape_server +3 -3
- data/lib/ape.rb +131 -937
- data/lib/ape/atomURI.rb +1 -1
- data/lib/ape/authent.rb +11 -17
- data/lib/ape/categories.rb +8 -7
- data/lib/ape/collection.rb +3 -7
- data/lib/ape/crumbs.rb +1 -1
- data/lib/ape/entry.rb +1 -1
- data/lib/ape/escaper.rb +1 -1
- data/lib/ape/feed.rb +26 -14
- data/lib/ape/handler.rb +8 -3
- data/lib/ape/html.rb +1 -1
- data/lib/ape/invoker.rb +1 -1
- data/lib/ape/invokers/deleter.rb +1 -1
- data/lib/ape/invokers/getter.rb +1 -1
- data/lib/ape/invokers/poster.rb +1 -1
- data/lib/ape/invokers/putter.rb +1 -1
- data/lib/ape/names.rb +1 -1
- data/lib/ape/print_writer.rb +4 -6
- data/lib/ape/reporter.rb +156 -0
- data/lib/ape/reporters/atom_reporter.rb +51 -0
- data/lib/ape/reporters/atom_template.eruby +38 -0
- data/lib/ape/reporters/html_reporter.rb +53 -0
- data/lib/ape/reporters/html_template.eruby +62 -0
- data/lib/ape/reporters/text_reporter.rb +37 -0
- data/lib/ape/samples.rb +30 -51
- data/lib/ape/server.rb +16 -4
- data/lib/ape/service.rb +1 -1
- data/lib/ape/util.rb +67 -0
- data/lib/ape/validator.rb +85 -57
- data/lib/ape/validator_dsl.rb +40 -0
- data/lib/ape/validators/entry_posts_validator.rb +226 -0
- data/lib/ape/validators/media_linkage_validator.rb +78 -0
- data/lib/ape/validators/media_posts_validator.rb +104 -0
- data/lib/ape/validators/sanitization_validator.rb +57 -0
- data/lib/ape/validators/schema_validator.rb +57 -0
- data/lib/ape/validators/service_document_validator.rb +64 -0
- data/lib/ape/validators/sorting_validator.rb +87 -0
- data/lib/ape/version.rb +1 -1
- data/{lib/ape/samples → samples}/atom_schema.txt +0 -0
- data/{lib/ape/samples → samples}/basic_entry.eruby +4 -4
- data/{lib/ape/samples → samples}/categories_schema.txt +0 -0
- data/{lib/ape/samples → samples}/mini_entry.eruby +0 -0
- data/{lib/ape/samples → samples}/service_schema.txt +0 -0
- data/{lib/ape/samples → samples}/unclean_xhtml_entry.eruby +0 -0
- data/test/test_helper.rb +33 -1
- data/test/unit/ape_test.rb +92 -0
- data/test/unit/authent_test.rb +2 -2
- data/test/unit/reporter_test.rb +102 -0
- data/test/unit/samples_test.rb +2 -2
- data/test/unit/validators_test.rb +50 -0
- data/{lib/ape/layout → web}/ape.css +5 -1
- data/{lib/ape/layout → web}/ape_logo.png +0 -0
- data/{lib/ape/layout → web}/index.html +2 -5
- data/{lib/ape/layout → web}/info.png +0 -0
- metadata +108 -56
- data/CHANGELOG +0 -1
- data/Manifest +0 -45
- data/ape.gemspec +0 -57
- data/scripts/go.rb +0 -29
data/LICENSE
CHANGED
data/README
CHANGED
@@ -1,12 +1,26 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
== Atom Protocol Exerciser (APE)
|
2
|
+
|
3
|
+
APE is a sanity-checker for implementations of the Atom Publishing Protocol (AtomPub or APP). It is written in Ruby,
|
4
|
+
and provides a Mongrel-based HTML interface describing its interactions with the APP implementation under test.
|
5
|
+
|
6
|
+
For more information about the history and impetus for the creation of APE, see Tim Bray's account here[http://www.tbray.org/ongoing/When/200x/2006/08/11/Meet-the-Ape].
|
4
7
|
|
5
|
-
==
|
8
|
+
== License
|
9
|
+
|
10
|
+
Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. See the included LICENSE[link:/files/LICENSE.html] file for details.
|
11
|
+
|
12
|
+
== Quick Start
|
13
|
+
|
14
|
+
Install APE via RubyGems:
|
15
|
+
|
16
|
+
$ gem install ape
|
17
|
+
|
18
|
+
Now, you should have the ape_server command available in your $PATH. Start the server with:
|
6
19
|
|
7
|
-
|
8
|
-
|
20
|
+
$ ape_server
|
21
|
+
|
22
|
+
This will start the server in the foreground. You can access APE in your browser at http://localhost:4000
|
9
23
|
|
10
|
-
==
|
24
|
+
== The Source
|
11
25
|
|
12
|
-
|
26
|
+
To access the latest source code for APE, see the project site at https://rubyforge.org/projects/ape
|
data/Rakefile
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/clean'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'rake/testtask'
|
5
|
+
|
6
|
+
$:.unshift File.dirname(__FILE__) + '/lib'
|
7
|
+
require 'ape'
|
8
|
+
|
9
|
+
def spec
|
10
|
+
spec ||= Gem::Specification.new do |s|
|
11
|
+
s.platform = Gem::Platform::RUBY
|
12
|
+
s.name = 'ape'
|
13
|
+
s.version = Ape::VERSION::STRING
|
14
|
+
s.author = 'Tim Bray'
|
15
|
+
s.email = 'tim.bray@sun.com'
|
16
|
+
s.homepage = 'ape.rubyforge.org'
|
17
|
+
s.summary = 'The Atom Protocol Exerciser'
|
18
|
+
|
19
|
+
s.files = FileList['lib/**/*', 'samples/*', 'test/**/*', 'web/*',
|
20
|
+
'README', 'LICENSE', 'Rakefile'].to_ary
|
21
|
+
s.bindir = 'bin'
|
22
|
+
s.executable = 'ape_server'
|
23
|
+
|
24
|
+
s.has_rdoc = false
|
25
|
+
s.extra_rdoc_files = ['README', 'LICENSE']
|
26
|
+
|
27
|
+
s.rubyforge_project = 'ape'
|
28
|
+
|
29
|
+
s.add_dependency 'rake', '>= 0.8'
|
30
|
+
s.add_dependency 'mongrel', '>= 1.1.3'
|
31
|
+
s.add_dependency 'erubis', '>= 2.5.0'
|
32
|
+
s.add_dependency 'rubyforge', '>= 0.4'
|
33
|
+
s.add_dependency 'mocha', '>= 0.9.0'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def install_gem(*args)
|
38
|
+
cmd = []
|
39
|
+
cmd << "#{'sudo ' unless Gem.win_platform?}gem install"
|
40
|
+
sh cmd.push(*args.flatten).join(" ")
|
41
|
+
end
|
42
|
+
|
43
|
+
desc 'Install the necessary dependencies'
|
44
|
+
task :setup do
|
45
|
+
installed = Gem::SourceIndex.from_installed_gems
|
46
|
+
dependencies = spec.dependencies
|
47
|
+
dependencies.select { |dep|
|
48
|
+
installed.search(dep.name, dep.version_requirements).empty? }.each do |dep|
|
49
|
+
puts "Installing #{dep} ..."
|
50
|
+
install_gem dep.name, "-v '#{dep.version_requirements.to_s}'"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# The default task is run if rake is given no explicit arguments.
|
55
|
+
desc "Default Task"
|
56
|
+
task :default => :test
|
57
|
+
|
58
|
+
# Test Tasks ---------------------------------------------------------
|
59
|
+
|
60
|
+
desc "Run all tests"
|
61
|
+
task :test => [:test_units]
|
62
|
+
|
63
|
+
Rake::TestTask.new("test_units") do |t|
|
64
|
+
t.test_files = FileList['test/unit/*test.rb']
|
65
|
+
t.verbose = false
|
66
|
+
end
|
data/bin/ape_server
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
# Copyright
|
2
|
+
# Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved
|
3
3
|
# Use is subject to license terms - see file "LICENSE"
|
4
4
|
$:.unshift File.dirname(__FILE__) + '/../lib'
|
5
5
|
|
@@ -16,11 +16,11 @@ OPTIONS = {
|
|
16
16
|
}
|
17
17
|
|
18
18
|
parser = OptionParser.new do |opts|
|
19
|
-
opts.banner = '
|
19
|
+
opts.banner = 'The ape server options:'
|
20
20
|
opts.separator ''
|
21
21
|
opts.on('-a', '--address ADDRESS', 'Address to bind to', "default: #{OPTIONS[:host]}") { |v| OPTIONS[:host] = v }
|
22
22
|
opts.on('-p', '--port PORT', 'Port to bind to', "default: #{OPTIONS[:port]}") { |v| OPTIONS[:port] = v }
|
23
|
-
opts.on('-d', '--directory DIRECTORY', 'ape home directory', "default: #{Ape::
|
23
|
+
opts.on('-d', '--directory DIRECTORY', 'ape home directory', "default: #{Ape::Ape.home}") { |v| OPTIONS[:home] = v }
|
24
24
|
opts.on('-h', '--help', 'Displays this help') { puts opts; exit }
|
25
25
|
opts.parse!(ARGV)
|
26
26
|
end
|
data/lib/ape.rb
CHANGED
@@ -1,982 +1,176 @@
|
|
1
|
-
# Copyright
|
2
|
-
#
|
1
|
+
# Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved
|
2
|
+
# See the included LICENSE[link:/files/LICENSE.html] file for details.
|
3
3
|
$:.unshift File.dirname(__FILE__)
|
4
|
-
|
5
|
-
require 'rubygems'
|
6
|
-
require 'rexml/document'
|
7
|
-
require 'builder'
|
8
|
-
|
9
|
-
Dir[File.dirname(__FILE__) + '/ape/*.rb'].each { |l| require l }
|
10
|
-
|
11
4
|
module Ape
|
5
|
+
require 'rubygems'
|
6
|
+
|
7
|
+
Dir[File.dirname(__FILE__) + '/ape/*.rb'].each { |l| require l }
|
8
|
+
|
9
|
+
@CONF = {}
|
10
|
+
|
11
|
+
def Ape.conf
|
12
|
+
@CONF
|
13
|
+
end
|
14
|
+
|
12
15
|
class Ape
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
else
|
19
|
-
raise ArgumentError, "output must be 'text' or 'html'"
|
20
|
-
end
|
21
|
-
|
22
|
-
@diarefs = {}
|
23
|
-
@dianum = 1
|
24
|
-
@@debugging = args[:debug]
|
25
|
-
@steps = []
|
26
|
-
@header = @footer = nil
|
27
|
-
@lnum = 1
|
28
|
-
@errors = @warnings = 0
|
16
|
+
attr_reader :aperc, :reporter
|
17
|
+
|
18
|
+
@@home = nil
|
19
|
+
def self.home=(home)
|
20
|
+
@@home = home
|
29
21
|
end
|
30
22
|
|
31
|
-
|
32
|
-
|
33
|
-
requested_e_coll = nil, requested_m_coll = nil)
|
34
|
-
|
35
|
-
# Google athent weirdness
|
36
|
-
@authent = Authent.new(username, password)
|
37
|
-
header(uri)
|
38
|
-
begin
|
39
|
-
might_fail(uri, requested_e_coll, requested_m_coll)
|
40
|
-
rescue Exception
|
41
|
-
error "Ouch! Ape fall down go boom; details: " +
|
42
|
-
"#{$!}\n#{$!.class}\n#{$!.backtrace}"
|
43
|
-
puts $!.backtrace.join("\n")
|
44
|
-
end
|
23
|
+
def self.home
|
24
|
+
@@home || ENV["APE_HOME"] || File.join(home_directory,".ape")
|
45
25
|
end
|
46
26
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
name = 'Retrieval of Service Document'
|
51
|
-
service = check_resource(uri, name, Names::AppMediaType)
|
52
|
-
return unless service
|
53
|
-
|
54
|
-
# * XML-parse the service doc
|
55
|
-
text = service.body
|
56
|
-
begin
|
57
|
-
service = REXML::Document.new(text, { :raw => nil })
|
58
|
-
rescue REXML::ParseException
|
59
|
-
prob = $!.to_s.gsub(/\n/, '<br/>')
|
60
|
-
error "Service document not well-formed: #{prob}"
|
61
|
-
return
|
62
|
-
end
|
63
|
-
|
64
|
-
# RNC-validate the service doc
|
65
|
-
Validator.validate(Samples.service_RNC, text, 'Service doc', self)
|
66
|
-
|
67
|
-
# * Do we have collections we can post an entry and a picture to?
|
68
|
-
# the requested_* arguments are the requested collection titles; if
|
69
|
-
# provided, try to match them, otherwise just pick the first listed
|
70
|
-
#
|
71
|
-
begin
|
72
|
-
collections = Service.collections(service, uri)
|
73
|
-
rescue Exception
|
74
|
-
error "Couldn't read collections from service doc: #{$!}"
|
75
|
-
return
|
76
|
-
end
|
77
|
-
entry_coll = media_coll = nil
|
78
|
-
if collections.length > 0
|
79
|
-
start_list "Found these collections"
|
80
|
-
collections.each do |collection|
|
81
|
-
list_item "'#{collection.title}' " +
|
82
|
-
"accepts #{collection.accept.join(', ')}"
|
83
|
-
if (!entry_coll) && collection.accept.index(Names::AtomEntryMediaType)
|
84
|
-
if requested_e_coll
|
85
|
-
if requested_e_coll == collection.title
|
86
|
-
entry_coll = collection
|
87
|
-
end
|
88
|
-
else
|
89
|
-
entry_coll = collection
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
if !media_coll
|
94
|
-
image_jpeg_ok = false
|
95
|
-
collection.accept.each do |types|
|
96
|
-
types.split(/, */).each do |type|
|
97
|
-
|
98
|
-
if type == '*/*' || type == 'image/*' || type == 'image/jpeg'
|
99
|
-
image_jpeg_ok = true
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
103
|
-
if image_jpeg_ok
|
104
|
-
if requested_m_coll
|
105
|
-
if requested_m_coll == collection.title
|
106
|
-
media_coll = collection
|
107
|
-
end
|
108
|
-
else
|
109
|
-
media_coll = collection
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
end_list
|
117
|
-
|
118
|
-
if entry_coll
|
119
|
-
info "Will use collection '#{entry_coll.title}' for entry creation."
|
120
|
-
test_entry_posts entry_coll
|
121
|
-
test_sorting entry_coll
|
122
|
-
test_sanitization entry_coll
|
123
|
-
else
|
124
|
-
warning "No collection for 'application/atom+xml;type=entry', won't test entry posting."
|
125
|
-
end
|
126
|
-
|
127
|
-
if media_coll
|
128
|
-
info "Will use collection '#{media_coll.title}' for media creation."
|
129
|
-
test_media_posts media_coll.href
|
130
|
-
test_media_linkage media_coll
|
131
|
-
else
|
132
|
-
warning "No collection for 'image/jpeg', won't test media posting."
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
def test_media_linkage(coll)
|
137
|
-
info "TESTING: Media collection re-ordering after PUT."
|
138
|
-
|
139
|
-
# We'll post three mini entries to the collection
|
140
|
-
data = Samples.picture
|
141
|
-
poster = Poster.new(coll.href, @authent)
|
142
|
-
['One', 'Two', 'Three'].each do |num|
|
143
|
-
slug = "Picture #{num}"
|
144
|
-
poster.set_header('Slug', slug)
|
145
|
-
name = "Posting pic #{num}"
|
146
|
-
worked = poster.post('image/jpeg', data)
|
147
|
-
save_dialog(name, poster)
|
148
|
-
if !worked
|
149
|
-
error("Can't POST Picture #{num}: #{poster.last_error}", name)
|
150
|
-
return
|
151
|
-
end
|
152
|
-
sleep 2
|
153
|
-
end
|
154
|
-
|
155
|
-
# grab the collection to gather the MLE ids
|
156
|
-
entries = Feed.read(coll.href, 'Pictures from multi-post', self, true)
|
157
|
-
if entries.size < 3
|
158
|
-
error "Pictures apparently not in collection"
|
159
|
-
return
|
160
|
-
end
|
161
|
-
|
162
|
-
ids = entries.map { |e| e.child_content('id)') }
|
163
|
-
|
164
|
-
# let's update one of them; have to fetch it first to get the ETag
|
165
|
-
two_media = entries[1].link('edit-media')
|
166
|
-
if !two_media
|
167
|
-
error "Second entry from feed doesn't have an 'edit-media' link."
|
168
|
-
return
|
169
|
-
end
|
170
|
-
two_resp = check_resource(two_media, 'Fetch image to get ETag', 'image/jpeg', true)
|
171
|
-
unless two_resp
|
172
|
-
error "Can't fetch image to get ETag"
|
173
|
-
return
|
174
|
-
end
|
175
|
-
etag = two_resp.header 'etag'
|
176
|
-
|
177
|
-
putter = Putter.new(two_media, @authent)
|
178
|
-
putter.set_header('If-Match', etag)
|
179
|
-
|
180
|
-
name = 'Updating one of three pix with PUT'
|
181
|
-
if putter.put('image/jpeg', data)
|
182
|
-
good "Update one of newly posted pictures went OK."
|
183
|
-
else
|
184
|
-
save_dialog(name, putter)
|
185
|
-
error("Can't update picture at #{two_media}", name)
|
186
|
-
return
|
187
|
-
end
|
188
|
-
|
189
|
-
# now the order should have changed
|
190
|
-
wanted = [ ids[2], ids[0], ids[1] ]
|
191
|
-
entries = Feed.read(coll.href, 'MLEs post-update', self, true)
|
192
|
-
entries.each do |from_feed|
|
193
|
-
want = wanted.pop
|
194
|
-
unless from_feed.child_content('id').eql?(want)
|
195
|
-
error "Updating bits failed to re-order link entries in media collection."
|
196
|
-
return
|
197
|
-
end
|
198
|
-
|
199
|
-
# next to godliness
|
200
|
-
delete_entry(from_feed)
|
201
|
-
|
202
|
-
break if wanted.empty?
|
203
|
-
end
|
204
|
-
good "Entries correctly ordered after update of multi-post."
|
205
|
-
|
27
|
+
#recipe from cap
|
28
|
+
def self.home_directory
|
29
|
+
ENV["HOME"] || (ENV["HOMEPATH"] && "#{ENV["HOMEDRIVE"]}#{ENV["HOMEPATH"]}") || "/"
|
206
30
|
end
|
207
31
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
return
|
218
|
-
end
|
219
|
-
|
220
|
-
location = poster.header('Location')
|
221
|
-
name = "Retrieval of unclean XHTML entry"
|
222
|
-
entry = check_resource(location, name, Names::AtomMediaType)
|
223
|
-
return unless entry
|
224
|
-
|
225
|
-
begin
|
226
|
-
entry = Entry.new(entry.body, location)
|
227
|
-
rescue REXML::ParseException
|
228
|
-
prob = $!.to_s.gsub(/\n/, '<br/>')
|
229
|
-
error "New entry is not well-formed: #{prob}"
|
230
|
-
return
|
231
|
-
end
|
232
|
-
|
233
|
-
no_problem = true
|
234
|
-
patterns = {
|
235
|
-
'//xhtml:script' => "Published entry retains xhtml:script element.",
|
236
|
-
'//*[@background]' => "Published entry retains 'background' attribute.",
|
237
|
-
'//*[@style]' => "Published entry retains 'style' attribute.",
|
238
|
-
|
239
|
-
}
|
240
|
-
patterns.each { |xp, message| warning(message) unless entry.xpath_match(xp).empty? }
|
241
|
-
|
242
|
-
entry.xpath_match('//xhtml:a').each do |a|
|
243
|
-
if a.attributes['href'] =~ /^([a-zA-Z]+):/
|
244
|
-
if $1 != 'http'
|
245
|
-
no_problem = false
|
246
|
-
warning "Published entry retains dangerous hyperlink: '#{a.attributes['href']}'."
|
247
|
-
end
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
delete_entry(entry)
|
252
|
-
|
253
|
-
good "Published entry appears to be sanitized." if no_problem
|
32
|
+
# Creates an Ape instance with options given in the +args+ Hash.
|
33
|
+
#
|
34
|
+
# ==== Options
|
35
|
+
# * :output - one of 'text' or 'html'. #report will output in this format. Defaults to 'html'.
|
36
|
+
# * :debug - enables debug information at each step in the output
|
37
|
+
def initialize(args = {})
|
38
|
+
output = args[:output] || 'html'
|
39
|
+
@reporter = Reporter.instance(output, args)
|
40
|
+
load File.join(Ape.home, 'aperc') if File.exist?(File.join(Ape.home, 'aperc'))
|
254
41
|
end
|
255
|
-
|
256
|
-
def test_sorting(coll)
|
257
|
-
|
258
|
-
info "TESTING: Collection re-ordering after PUT."
|
259
|
-
|
260
|
-
# We'll post three mini entries to the collection
|
261
|
-
poster = Poster.new(coll.href, @authent)
|
262
|
-
['One', 'Two', 'Three'].each do |num|
|
263
|
-
sleep 2
|
264
|
-
text = Samples.mini_entry.gsub('Mini-1', "Mini #{num}")
|
265
|
-
name = "Posting Mini #{num}"
|
266
|
-
worked = poster.post(Names::AtomEntryMediaType, text)
|
267
|
-
save_dialog(name, poster)
|
268
|
-
if !worked
|
269
|
-
error("Can't POST Mini #{name}: #{poster.last_error}", name)
|
270
|
-
return
|
271
|
-
end
|
272
|
-
end
|
273
42
|
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
two = from_feed if want == 'Mini Two'
|
285
|
-
break if wanted.empty?
|
286
|
-
end
|
287
|
-
good "Entries correctly ordered after multi-post."
|
288
|
-
|
289
|
-
# let's update one of them; have to fetch it first to get the ETag
|
290
|
-
link = two.link('edit', self)
|
291
|
-
unless link
|
292
|
-
error "Can't check entry without edit link, entry id: #{two.get_child('id/text()')}"
|
293
|
-
return
|
294
|
-
end
|
295
|
-
two_resp = check_resource(link, 'fetch two', Names::AtomMediaType, false)
|
296
|
-
|
297
|
-
correctly_ordered = false
|
298
|
-
if two_resp
|
299
|
-
etag = two_resp.header 'etag'
|
300
|
-
|
301
|
-
putter = Putter.new(link, @authent)
|
302
|
-
putter.set_header('If-Match', etag)
|
303
|
-
|
304
|
-
name = 'Updating mini-entry with PUT'
|
305
|
-
sleep 2
|
306
|
-
updated = two_resp.body.gsub('Mini Two', 'Mini-4')
|
307
|
-
unless putter.put(Names::AtomEntryMediaType, updated)
|
308
|
-
save_dialog(name, putter)
|
309
|
-
error("Can't update mini-entry at #{link}", name)
|
310
|
-
return
|
311
|
-
end
|
312
|
-
# now the order should have changed
|
313
|
-
wanted = ['Mini One', 'Mini Three', 'Mini-4']
|
314
|
-
correctly_ordered = true
|
315
|
-
else
|
316
|
-
error "Mini Two entry not received. Can't assure the correct order after update."
|
317
|
-
wanted = ['Mini One', 'Mini Two', 'Mini Three']
|
318
|
-
end
|
43
|
+
# Checks the AtomPub server at +uri+ for sanity.
|
44
|
+
#
|
45
|
+
# ==== Options
|
46
|
+
# * uri - the URI of the AtomPub server. Required.
|
47
|
+
# * username - an optional username for authentication
|
48
|
+
# * password - if a username is provided, a password is required. See Ape::Authent for more information.
|
49
|
+
# * requested_e_coll - a preferred entry collection to check
|
50
|
+
# * requested_m_coll - a preferred media collection to check
|
51
|
+
def check(uri, username=nil, password=nil,
|
52
|
+
requested_e_coll = nil, requested_m_coll = nil)
|
319
53
|
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
delete_entry(from_feed)
|
330
|
-
|
331
|
-
break if wanted.empty?
|
54
|
+
@authent = Authent.new(username, password)
|
55
|
+
reporter.header = uri
|
56
|
+
::Ape.conf[:REQUESTED_ENTRY_COLLECTION] = requested_e_coll if requested_e_coll
|
57
|
+
::Ape.conf[:REQUESTED_MEDIA_COLLECTION] = requested_m_coll if requested_m_coll
|
58
|
+
begin
|
59
|
+
might_fail(uri)
|
60
|
+
rescue Exception
|
61
|
+
reporter.error(self, "Ouch! Ape fall down go boom; details: " +
|
62
|
+
"#{$!}\n#{$!.class}\n#{$!.backtrace}")
|
332
63
|
end
|
333
|
-
good "Entries correctly ordered after update of multi-post." if correctly_ordered
|
334
|
-
|
335
64
|
end
|
336
65
|
|
337
|
-
def
|
66
|
+
def might_fail(uri)
|
67
|
+
service_validator = Validator.instance(:service_document, @reporter, @authent)
|
68
|
+
service_validator.validate(:uri => uri)
|
69
|
+
@service = service_validator.service_document
|
70
|
+
@entry_collections = service_validator.entry_collections
|
71
|
+
@media_collections = service_validator.media_collections
|
338
72
|
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
# * List the current entries, remember which IDs we've seen
|
343
|
-
info "TESTING: Entry-posting basics."
|
344
|
-
ids = []
|
345
|
-
unless entries.empty?
|
346
|
-
start_list "Now in the Entries feed"
|
347
|
-
entries.each do |entry|
|
348
|
-
list_item entry.summarize
|
349
|
-
ids << entry.child_content('id')
|
350
|
-
end
|
351
|
-
end_list
|
352
|
-
end
|
353
|
-
|
354
|
-
# Setting up to post a new entry
|
355
|
-
poster = Poster.new(collection_uri, @authent)
|
356
|
-
if poster.last_error
|
357
|
-
error("Unacceptable URI for '#{entry_collection.title}' collection: " +
|
358
|
-
poster.last_error)
|
359
|
-
return
|
360
|
-
end
|
361
|
-
|
362
|
-
my_entry = Entry.new(Samples.basic_entry)
|
363
|
-
|
364
|
-
# ask it to use this in the URI
|
365
|
-
slug_num = rand(100000)
|
366
|
-
slug = "ape-#{slug_num}"
|
367
|
-
slug_re = %r{ape.?#{slug_num}}
|
368
|
-
poster.set_header('Slug', slug)
|
369
|
-
|
370
|
-
# add some categories to the entry, and remember which
|
371
|
-
@cats = Categories.add_cats(my_entry, entry_collection, @authent, self)
|
372
|
-
|
373
|
-
# * OK, post it
|
374
|
-
worked = poster.post(Names::AtomEntryMediaType, my_entry.to_s)
|
375
|
-
name = 'Posting new entry'
|
376
|
-
save_dialog(name, poster)
|
377
|
-
if !worked
|
378
|
-
error("Can't POST new entry: #{poster.last_error}", name)
|
379
|
-
return
|
380
|
-
end
|
381
|
-
|
382
|
-
location = poster.header('Location')
|
383
|
-
unless location
|
384
|
-
error("No Location header upon POST creation", name)
|
385
|
-
return
|
386
|
-
end
|
387
|
-
good("Posting of new entry to the Entries collection " +
|
388
|
-
"reported success, Location: #{location}", name)
|
389
|
-
|
390
|
-
info "Examining the new entry as returned in the POST response"
|
391
|
-
check_new_entry(my_entry, poster.entry, "Returned entry") if poster.entry
|
392
|
-
|
393
|
-
# * See if the Location uri can be retrieved, and check its consistency
|
394
|
-
name = "Retrieval of newly created entry"
|
395
|
-
new_entry = check_resource(location, name, Names::AtomMediaType)
|
396
|
-
return unless new_entry
|
397
|
-
|
398
|
-
# Grab its etag
|
399
|
-
etag = new_entry.header 'etag'
|
400
|
-
|
401
|
-
info "Examining the new entry as retrieved using Location header in POST response:"
|
402
|
-
|
403
|
-
begin
|
404
|
-
new_entry = Entry.new(new_entry.body, location)
|
405
|
-
rescue REXML::ParseException
|
406
|
-
prob = $!.to_s.gsub(/\n/, '<br/>')
|
407
|
-
error "New entry is not well-formed: #{prob}"
|
408
|
-
return
|
409
|
-
end
|
410
|
-
|
411
|
-
# * See if the slug was used
|
412
|
-
slug_used = false
|
413
|
-
new_entry.alt_links.each do |a|
|
414
|
-
href = a.attributes['href']
|
415
|
-
if href && href.index(slug_re)
|
416
|
-
slug_used = true
|
73
|
+
if @entry_collections && true != ::Ape.conf[:ENTRY_VALIDATION_DISABLED]
|
74
|
+
[:entry_posts, :sorting, :sanitization].each do |option|
|
75
|
+
check_validator(option)
|
417
76
|
end
|
418
|
-
end
|
419
|
-
if slug_used
|
420
|
-
good "Client-provided slug '#{slug}' was used in server-generated URI."
|
421
77
|
else
|
422
|
-
warning "
|
423
|
-
end
|
424
|
-
|
425
|
-
check_new_entry(my_entry, new_entry, "Retrieved entry")
|
426
|
-
|
427
|
-
entry_id = new_entry.child_content('id')
|
428
|
-
|
429
|
-
# * fetch the feed again and check that version
|
430
|
-
from_feed = find_entry(collection_uri, "entry collection", entry_id)
|
431
|
-
if from_feed.class == String
|
432
|
-
good "About to check #{collection_uri}"
|
433
|
-
Feed.read(collection_uri, "Can't find entry in collection", self)
|
434
|
-
error "New entry didn't show up in the collections feed."
|
435
|
-
return
|
436
|
-
end
|
437
|
-
|
438
|
-
info "Examining the new entry as it appears in the collection feed:"
|
439
|
-
|
440
|
-
# * Check the entry from the feed
|
441
|
-
check_new_entry(my_entry, from_feed, "Entry from collection feed")
|
442
|
-
|
443
|
-
edit_uri = new_entry.link('edit', self)
|
444
|
-
if !edit_uri
|
445
|
-
error "Entry from Location header has no edit link."
|
446
|
-
return
|
78
|
+
reporter.warning(self, "No collection for 'application/atom+xml;type=entry', won't test entry posting.")
|
447
79
|
end
|
448
80
|
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
# Conditional PUT if an etag
|
454
|
-
putter.set_header('If-Match', etag) if etag
|
455
|
-
|
456
|
-
new_title = "Let’s all do the Ape!"
|
457
|
-
new_text = Samples.retitled_entry(new_title, entry_id)
|
458
|
-
response = putter.put(Names::AtomEntryMediaType, new_text)
|
459
|
-
save_dialog(name, putter)
|
460
|
-
|
461
|
-
if response
|
462
|
-
good("Update of new entry reported success.", name)
|
463
|
-
from_feed = find_entry(collection_uri, "entry collection", entry_id)
|
464
|
-
if from_feed.class == String
|
465
|
-
check_resource(collection_uri, "Check collection after lost update", nil, true)
|
466
|
-
error "Updated entry ID #{entry_id} not found in entries collection."
|
467
|
-
return
|
468
|
-
end
|
469
|
-
if from_feed.child_content('title') == new_title
|
470
|
-
good "Title of new entry successfully updated."
|
471
|
-
else
|
472
|
-
warning "After PUT update of title, Expected " +
|
473
|
-
"'#{new_title}', but saw '#{from_feed.child_content('title')}'"
|
81
|
+
if @media_collections && true != ::Ape.conf[:MEDIA_VALIDATION_DISABLED]
|
82
|
+
[:media_posts, :media_linkage].each do |option|
|
83
|
+
check_validator(option)
|
474
84
|
end
|
475
85
|
else
|
476
|
-
warning("
|
477
|
-
end
|
478
|
-
|
479
|
-
# the edit-uri might have changed
|
480
|
-
return unless delete_entry(from_feed, 'New Entry deletion')
|
481
|
-
|
482
|
-
# See if it's gone from the feed
|
483
|
-
still_there = find_entry(collection_uri, "entry collection", entry_id)
|
484
|
-
if still_there.class != String
|
485
|
-
error "Entry is still in collection post-deletion."
|
486
|
-
else
|
487
|
-
good "Entry not found in feed after deletion."
|
488
|
-
end
|
489
|
-
|
490
|
-
end
|
491
|
-
|
492
|
-
def test_media_posts media_collection
|
493
|
-
|
494
|
-
info "TESTING: Posting to media collection."
|
495
|
-
|
496
|
-
# * Post a picture to the media collection
|
497
|
-
#
|
498
|
-
poster = Poster.new(media_collection, @authent)
|
499
|
-
if poster.last_error
|
500
|
-
error("Unacceptable URI for '#{media_coll.title}' collection: " +
|
501
|
-
poster.last_error)
|
502
|
-
return
|
503
|
-
end
|
504
|
-
|
505
|
-
name = 'Post image to media collection'
|
506
|
-
|
507
|
-
# ask it to use this in the URI
|
508
|
-
slug_num = rand(100000)
|
509
|
-
slug = "apix-#{slug_num}"
|
510
|
-
slug_re = %r{apix.?#{slug_num}}
|
511
|
-
poster.set_header('Slug', slug)
|
512
|
-
|
513
|
-
#poster.set_header('Slug', slug)
|
514
|
-
worked = poster.post('image/jpeg', Samples.picture)
|
515
|
-
save_dialog(name, poster)
|
516
|
-
if !worked
|
517
|
-
error("Can't POST picture to media collection: #{poster.last_error}",
|
518
|
-
name)
|
519
|
-
return
|
86
|
+
reporter.warning(self, "No collection for 'image/jpeg', won't test media posting.")
|
520
87
|
end
|
521
|
-
|
522
|
-
good("Post of image file reported success, media link location: " +
|
523
|
-
"#{poster.header('Location')}", name)
|
524
88
|
|
525
|
-
#
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
return unless media_link_entry
|
530
|
-
|
531
|
-
if media_link_entry.last_error
|
532
|
-
error "Can't proceed with media-post testing."
|
533
|
-
return
|
534
|
-
end
|
535
|
-
|
536
|
-
# * See if the <content src= is there and usable
|
537
|
-
begin
|
538
|
-
media_link_entry = Entry.new(media_link_entry.body, mle_uri)
|
539
|
-
rescue REXML::ParseException
|
540
|
-
prob = $!.to_s.gsub(/\n/, '<br/>')
|
541
|
-
error "Media link entry is not well-formed: #{prob}"
|
542
|
-
return
|
543
|
-
end
|
544
|
-
content_src = media_link_entry.content_src
|
545
|
-
if (!content_src) || (content_src == "")
|
546
|
-
error "Media link entry has no content@src pointer to media resource."
|
547
|
-
return
|
89
|
+
#custom validators
|
90
|
+
Validator.custom_validators(@reporter, @authent).each do |validator|
|
91
|
+
opts = check_manifest(validator)
|
92
|
+
break if !validator.validate(opts) && validator.deterministic?
|
548
93
|
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def report(output=STDOUT)
|
97
|
+
@reporter.report(output)
|
98
|
+
end
|
549
99
|
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
end
|
100
|
+
def check_manifest(validator)
|
101
|
+
variables = {}
|
102
|
+
manifest = validator.manifest
|
103
|
+
variables[:service_doc] = @service if (manifest.include?(:service_doc))
|
104
|
+
manifest.delete(:service_doc)
|
556
105
|
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
return unless picture
|
562
|
-
|
563
|
-
if picture.body == Samples.picture
|
564
|
-
good "Media resource was apparently stored and retrieved properly."
|
565
|
-
else
|
566
|
-
warning "Media resource differs from posted picture"
|
567
|
-
end
|
568
|
-
|
569
|
-
# * Delete the media link entry
|
570
|
-
return unless delete_entry(media_link_entry, 'Deletion of media link entry')
|
571
|
-
|
572
|
-
# * media link entry still in feed?
|
573
|
-
still_there = find_entry(media_collection, "media collection", media_link_id)
|
574
|
-
if still_there.class != String
|
575
|
-
error "Media link entry is still in collection post-deletion."
|
576
|
-
else
|
577
|
-
good "Media link entry no longer in feed."
|
106
|
+
if (manifest.include?(:entry_collection))
|
107
|
+
variables[:entry_collection] = ::Ape.conf[:REQUESTED_ENTRY_COLLECTION].nil? ? @entry_collections.first :
|
108
|
+
get_collection(@entry_collections, ::Ape.conf[:REQUESTED_ENTRY_COLLECTION])
|
109
|
+
manifest.delete(:entry_collection)
|
578
110
|
end
|
579
111
|
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
else
|
585
|
-
good "Media resource no longer fetchable."
|
112
|
+
if (manifest.include?(:media_collection))
|
113
|
+
variables[:media_collection] = ::Ape.conf[:REQUESTED_MEDIA_COLLECTION].nil? ? @media_collections.first :
|
114
|
+
get_collection(@media_collections, ::Ape.conf[:REQUESTED_MEDIA_COLLECTION])
|
115
|
+
manifest.delete(:media_collection)
|
586
116
|
end
|
587
117
|
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
dc_subject = new_entry.child_content(Samples.foreign_child, Samples.foreign_namespace)
|
608
|
-
if dc_subject
|
609
|
-
if dc_subject == Samples.foreign_child_content
|
610
|
-
good "Server preserved foreign markup in #{desc}."
|
611
|
-
else
|
612
|
-
warning "Server altered content of foreign markup in #{desc}."
|
613
|
-
end
|
614
|
-
else
|
615
|
-
warning "Server discarded foreign markup in #{desc}."
|
616
|
-
end
|
617
|
-
end
|
618
|
-
|
619
|
-
#
|
620
|
-
# End of tests; support functions from here down
|
621
|
-
#
|
622
|
-
|
623
|
-
# Fetch a feed and look up an entry by ID in it
|
624
|
-
def find_entry(feed_uri, name, id, report=false)
|
625
|
-
entries = Feed.read(feed_uri, name, self, report)
|
626
|
-
entries.each do |from_feed|
|
627
|
-
return from_feed if id == from_feed.child_content('id')
|
628
|
-
end
|
629
|
-
|
630
|
-
return "Couldn't find id #{id} in feed #{feed_uri}"
|
631
|
-
end
|
632
|
-
|
633
|
-
# remember the dialogue that the get/put/post/delete actor recorded
|
634
|
-
def save_dialog(name, actor)
|
635
|
-
@dialogs[name] = actor.crumbs if @dialogs
|
636
|
-
end
|
637
|
-
|
638
|
-
# Get a resource, optionally check its content-type
|
639
|
-
def check_resource(uri, name, content_type, report=true)
|
640
|
-
resource = Getter.new(uri, @authent)
|
641
|
-
|
642
|
-
# * Check the URI
|
643
|
-
if resource.last_error
|
644
|
-
error("Unacceptable #{name} URI: " + resource.last_error, name) if report
|
645
|
-
return nil
|
646
|
-
end
|
647
|
-
|
648
|
-
# * Get it, make sure it has the right content-type
|
649
|
-
worked = resource.get(content_type)
|
650
|
-
@dialogs[name] = resource.crumbs if @dialogs
|
651
|
-
|
652
|
-
if (resource.security_warning and not @security_warning)
|
653
|
-
@security_warning = true
|
654
|
-
warning("Sending authentication information over a open channel is not a good security practice.", name)
|
655
|
-
end
|
656
|
-
|
657
|
-
if !worked
|
658
|
-
# oops, couldn't even get get it
|
659
|
-
error("#{name} failed: " + resource.last_error, name) if report
|
660
|
-
return nil
|
661
|
-
|
662
|
-
elsif resource.last_error
|
663
|
-
# oops, media-type problem
|
664
|
-
error("#{name}: #{resource.last_error}", name) if report
|
665
|
-
|
666
|
-
else
|
667
|
-
# resource fetched and is of right type
|
668
|
-
good("#{name}: it exists and is served properly.", name) if report
|
669
|
-
end
|
670
|
-
|
671
|
-
return resource
|
672
|
-
end
|
673
|
-
|
674
|
-
def header(uri)
|
675
|
-
@header = "APP Service doc: #{uri}"
|
676
|
-
end
|
677
|
-
|
678
|
-
def footer(message)
|
679
|
-
@footer = message
|
680
|
-
end
|
681
|
-
|
682
|
-
def show_crumbs key
|
683
|
-
@dialogs[key].each do |d|
|
684
|
-
puts "D: #{d}"
|
685
|
-
end
|
686
|
-
end
|
687
|
-
|
688
|
-
def warning(message, crumb_key=nil)
|
689
|
-
@warnings += 1
|
690
|
-
if @dialogs
|
691
|
-
step "D#{crumb_key}" if crumb_key
|
692
|
-
show_crumbs(crumb_key) if crumb_key && @@debugging
|
693
|
-
end
|
694
|
-
step "W" + message
|
695
|
-
end
|
696
|
-
|
697
|
-
def error(message, crumb_key=nil)
|
698
|
-
@errors += 1
|
699
|
-
if @dialogs
|
700
|
-
step "D#{crumb_key}" if crumb_key
|
701
|
-
show_crumbs(crumb_key) if crumb_key && @@debugging
|
702
|
-
end
|
703
|
-
step "E" + message
|
704
|
-
end
|
705
|
-
|
706
|
-
def good(message, crumb_key=nil)
|
707
|
-
if @dialogs
|
708
|
-
step "D#{crumb_key}" if crumb_key
|
709
|
-
show_crumbs(crumb_key) if crumb_key && @@debugging
|
710
|
-
end
|
711
|
-
step "G" + message
|
712
|
-
end
|
713
|
-
|
714
|
-
def info(message)
|
715
|
-
step "I" + message
|
716
|
-
end
|
717
|
-
|
718
|
-
def step(message)
|
719
|
-
puts "PROGRESS: #{message[1..-1]}" if @@debugging
|
720
|
-
@steps << message
|
721
|
-
end
|
722
|
-
|
723
|
-
def start_list(message)
|
724
|
-
step [ message + ":" ]
|
725
|
-
end
|
726
|
-
|
727
|
-
def list_item(message)
|
728
|
-
@steps[-1] << message
|
729
|
-
end
|
730
|
-
|
731
|
-
def end_list
|
732
|
-
end
|
733
|
-
|
734
|
-
def line
|
735
|
-
printf "%2d. ", @lnum
|
736
|
-
@lnum += 1
|
737
|
-
end
|
738
|
-
|
739
|
-
def report(output=STDOUT)
|
740
|
-
if @output == 'text'
|
741
|
-
report_text output
|
742
|
-
else
|
743
|
-
report_html output
|
744
|
-
end
|
745
|
-
end
|
746
|
-
|
747
|
-
def report_html(output=STDOUT)
|
748
|
-
dialog = nil
|
749
|
-
|
750
|
-
if output == STDOUT
|
751
|
-
output.puts "Status: 200 OK\r"
|
752
|
-
output.puts "Content-type: text/html; charset=utf-8\r"
|
753
|
-
output.puts "\r"
|
754
|
-
end
|
755
|
-
|
756
|
-
@w = Builder::XmlMarkup.new(:target => output)
|
757
|
-
@w.html do
|
758
|
-
@w.head do
|
759
|
-
@w.title { @w.text! 'Atom Protocol Exerciser Report' }
|
760
|
-
@w.text! "\n"
|
761
|
-
@w.link(:rel => 'stylesheet', :type => 'text/css',:href => '../ape/ape.css' )
|
762
|
-
end
|
763
|
-
@w.text! "\n"
|
764
|
-
@w.body do
|
765
|
-
@w.h2 { @w.text! 'The Ape says:' }
|
766
|
-
@w.text! "\n"
|
767
|
-
if @header
|
768
|
-
@w.p { @w.text! @header }
|
769
|
-
@w.p do
|
770
|
-
@w.text! "Summary: "
|
771
|
-
@w.text!((@errors == 1) ? '1 error, ' : "#{@errors} errors, ")
|
772
|
-
@w.text!((@warnings == 1) ? '1 warning.' : "#{@warnings} warnings.")
|
773
|
-
end
|
774
|
-
@w.text! "\n"
|
775
|
-
end
|
776
|
-
@w.ol do
|
777
|
-
@w.text! "\n"
|
778
|
-
@steps.each do |step|
|
779
|
-
if step.kind_of? Array
|
780
|
-
# it's a list; no dialog applies
|
781
|
-
@w.li do
|
782
|
-
@w.p do
|
783
|
-
write_mark :info
|
784
|
-
@w.text! " #{step[0]}\n"
|
785
|
-
end
|
786
|
-
@w.ul do
|
787
|
-
step[1 .. -1].each { |li| report_li(nil, nil, li) }
|
788
|
-
end
|
789
|
-
@w.text! "\n"
|
790
|
-
end
|
791
|
-
else
|
792
|
-
body = step[1 .. -1]
|
793
|
-
opcode = step[0,1]
|
794
|
-
if opcode == "D"
|
795
|
-
dialog = body
|
796
|
-
else
|
797
|
-
case opcode
|
798
|
-
when "W" then report_li(dialog, :question, body)
|
799
|
-
when "E" then report_li(dialog, :exclamation, body)
|
800
|
-
when "G" then report_li(dialog, :check, body)
|
801
|
-
when "I" then report_li(dialog, :info, body)
|
802
|
-
else
|
803
|
-
line
|
804
|
-
puts "HUH? #{step}"
|
118
|
+
manifest.each do |option|
|
119
|
+
if (option.instance_of?(Hash))
|
120
|
+
all_collections = @entry_collections + @media_collections
|
121
|
+
option.each do |key, value|
|
122
|
+
unless (value.instance_of?(Hash))
|
123
|
+
#request a collection by its title, i.e: :entry_collection => 'Posts'
|
124
|
+
variables[key] = get_collection(all_collections, value)
|
125
|
+
else
|
126
|
+
#request the first collection that matches the options,
|
127
|
+
# i.e: :entry_collection => {:accept => 'image/png'}
|
128
|
+
# :entry_collection => {:title => 'Atachments', :accept => 'video/*'}
|
129
|
+
hash = value
|
130
|
+
variables[key] = all_collections.select do |collection|
|
131
|
+
matches = nil
|
132
|
+
hash.each do |k, v|
|
133
|
+
begin
|
134
|
+
matches = eval("collection.#{k.to_s}", binding, __FILE__, __LINE__).index(v)
|
135
|
+
rescue
|
136
|
+
raise ValidationError, "collection attribute not found: #{k.to_s}"
|
805
137
|
end
|
806
|
-
dialog = nil
|
807
138
|
end
|
808
|
-
|
139
|
+
collection if matches
|
140
|
+
end.first
|
809
141
|
end
|
810
142
|
end
|
811
|
-
|
812
|
-
@w.text! "\n"
|
813
|
-
if @footer then @w.p { @w.text! @footer } end
|
814
|
-
@w.text! "\n"
|
815
|
-
|
816
|
-
#unless @dialog.nil?
|
817
|
-
if @dialogs
|
818
|
-
@w.h2 { @w.text! 'Recorded client/server dialogs' }
|
819
|
-
@w.text! "\n"
|
820
|
-
@diarefs.each do |k, v|
|
821
|
-
dialog = @dialogs[k]
|
822
|
-
@w.h3(:id => "dia-#{v}") do
|
823
|
-
@w.text! k
|
824
|
-
end
|
825
|
-
@w.div(:class => 'dialog') do
|
826
|
-
|
827
|
-
@w.div(:class => 'dialab') do
|
828
|
-
@w.text! "\nTo server:\n"
|
829
|
-
dialog.grep(/^>/).each { |crumb| show_message(crumb, :to) }
|
830
|
-
end
|
831
|
-
@w.div( :class => 'dialab' ) do
|
832
|
-
@w.text! "\nFrom Server:\n"
|
833
|
-
dialog.grep(/^</).each { |crumb| show_message(crumb, :from) }
|
834
|
-
end
|
835
|
-
end
|
836
|
-
end
|
837
|
-
end
|
838
|
-
end
|
839
|
-
end
|
840
|
-
end
|
841
|
-
|
842
|
-
def report_li(dialog, marker, text)
|
843
|
-
@w.li do
|
844
|
-
@w.p do
|
845
|
-
if marker
|
846
|
-
write_mark marker
|
847
|
-
@w.text! ' '
|
848
|
-
end
|
849
|
-
# preserve line-breaks in output
|
850
|
-
lines = text.split("\n")
|
851
|
-
lines[0 .. -2].each do |line|
|
852
|
-
@w.text! line
|
853
|
-
@w.br
|
854
|
-
end
|
855
|
-
@w.text! lines[-1] if lines[-1]
|
856
|
-
|
857
|
-
if dialog
|
858
|
-
@w.a(:class => 'diaref', :href => "#dia-#{@dianum}") do
|
859
|
-
@w.text! ' [Dialog]'
|
860
|
-
end
|
861
|
-
@diarefs[dialog] = @dianum
|
862
|
-
@dianum += 1
|
863
|
-
end
|
864
|
-
end
|
865
|
-
end
|
866
|
-
@w.text! "\n"
|
867
|
-
end
|
868
|
-
|
869
|
-
def show_message(crumb, tf)
|
870
|
-
message = crumb[1 .. -1]
|
871
|
-
message.gsub!(/^\s*"/, '')
|
872
|
-
message.gsub!(/"\s*$/, '')
|
873
|
-
message.gsub!(/\\"/, '"')
|
874
|
-
message = Escaper.escape message
|
875
|
-
message.gsub!(/\\n/, "\n<br/>")
|
876
|
-
message.gsub!(/\\t/, '    ')
|
877
|
-
@w.div(:class => tf) { @w.target! << message }
|
878
|
-
end
|
879
|
-
|
880
|
-
def report_text(output=STDOUT)
|
881
|
-
output.puts @header if @header
|
882
|
-
@steps.each do |step|
|
883
|
-
if step.class == Crumbs
|
884
|
-
output.puts " Dialog:"
|
885
|
-
step.each { |crumb| output.puts " #{crumb}" }
|
886
|
-
else
|
887
|
-
body = step[1 .. -1]
|
888
|
-
case step[0,1]
|
889
|
-
when "W"
|
890
|
-
line
|
891
|
-
output.puts "WARNING: #{body}"
|
892
|
-
when "E"
|
893
|
-
line
|
894
|
-
output.puts "ERROR: #{body}"
|
895
|
-
when "G"
|
896
|
-
line
|
897
|
-
output.puts body
|
898
|
-
when "L"
|
899
|
-
line
|
900
|
-
output.puts body
|
901
|
-
when "e"
|
902
|
-
# no-op
|
903
|
-
when "I"
|
904
|
-
output.puts " #{body}"
|
905
|
-
when "D"
|
906
|
-
# later, dude
|
907
|
-
else
|
908
|
-
line
|
909
|
-
output.puts "HUH? #{body}"
|
910
|
-
end
|
911
143
|
end
|
912
|
-
output.puts @footer if @footer
|
913
144
|
end
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
problems += 1 if compare1(e1, e2, e1Name, e2Name, field)
|
920
|
-
end
|
921
|
-
return problems == 0
|
922
|
-
end
|
923
|
-
|
924
|
-
def compare1(e1, e2, e1Name, e2Name, field)
|
925
|
-
c1 = e1.child_content(field)
|
926
|
-
c2 = e2.child_content(field)
|
927
|
-
if c1 != c2
|
928
|
-
problem = true
|
929
|
-
if c1 == nil
|
930
|
-
warning "'#{field}' absent in #{e1Name}."
|
931
|
-
elsif c2 == nil
|
932
|
-
warning "'#{field}' absent in #{e2Name}."
|
933
|
-
else
|
934
|
-
t1 = e1.child_type(field)
|
935
|
-
t2 = e2.child_type(field)
|
936
|
-
if t1 != t2
|
937
|
-
warning "'#{field}' has type='#{t1}' " +
|
938
|
-
"in #{e1Name}, type='#{t2}' in #{e2Name}."
|
939
|
-
else
|
940
|
-
c1 = Escaper.escape(c1)
|
941
|
-
c2 = Escaper.escape(c2)
|
942
|
-
warning "'#{field}' in #{e1Name} [#{c1}] " +
|
943
|
-
"differs from that in #{e2Name} [#{c2}]."
|
944
|
-
end
|
945
|
-
end
|
145
|
+
|
146
|
+
#ensure all variables are setted
|
147
|
+
raise ValidationError, "#{manifest.join("\n")} haven't been setted" if variables.empty?
|
148
|
+
variables.each do |k, v|
|
149
|
+
raise ValidationError, "#{k} haven't been setted" unless v
|
946
150
|
end
|
947
|
-
|
151
|
+
|
152
|
+
variables
|
948
153
|
end
|
154
|
+
|
155
|
+
private
|
949
156
|
|
950
|
-
def
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
when :question
|
955
|
-
@w.span(:class => 'warning') { @w.text! '?' }
|
956
|
-
when :exclamation
|
957
|
-
@w.span(:class => 'error') { @w.text! '!' }
|
958
|
-
when :info
|
959
|
-
@w.img(:align => 'top', :src => '../ape/info.png')
|
960
|
-
end
|
157
|
+
def check_validator(option)
|
158
|
+
validator = Validator.instance(option, @reporter, @authent)
|
159
|
+
opts = check_manifest(validator)
|
160
|
+
validator.validate(opts)
|
961
161
|
end
|
962
162
|
|
963
|
-
def
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
return
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
good("Entry deletion reported success.", name)
|
975
|
-
else
|
976
|
-
error("Couldn't delete the entry: " + deleter.last_error, name)
|
977
|
-
end
|
978
|
-
return worked
|
163
|
+
def get_collection(collections, requested)
|
164
|
+
if (requested.instance_of?(Integer))
|
165
|
+
return collections[requested]
|
166
|
+
elsif (requested.to_sym == :first)
|
167
|
+
return collections.first
|
168
|
+
elsif (requested.to_sym == :last)
|
169
|
+
return collections.last
|
170
|
+
end
|
171
|
+
collections.select do |coll|
|
172
|
+
coll if (coll.title == requested)
|
173
|
+
end.first
|
979
174
|
end
|
980
175
|
end
|
981
176
|
end
|
982
|
-
|