ape 1.0.0 → 1.5.0
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/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/lib/ape/validator.rb
CHANGED
@@ -1,65 +1,93 @@
|
|
1
|
-
# Copyright © 2006 Sun Microsystems, Inc. All rights reserved
|
2
|
-
# Use is subject to license terms - see file "LICENSE"
|
3
|
-
|
4
|
-
if RUBY_PLATFORM =~ /java/
|
5
|
-
require 'java'
|
6
|
-
CompactSchemaReader = com.thaiopensource.validate.rng.CompactSchemaReader
|
7
|
-
ValidationDriver = com.thaiopensource.validate.ValidationDriver
|
8
|
-
StringReader = java.io.StringReader
|
9
|
-
StringWriter = java.io.StringWriter
|
10
|
-
InputSource = org.xml.sax.InputSource
|
11
|
-
ErrorHandlerImpl = com.thaiopensource.xml.sax.ErrorHandlerImpl
|
12
|
-
PropertyMapBuilder = com.thaiopensource.util.PropertyMapBuilder
|
13
|
-
ValidateProperty = com.thaiopensource.validate.ValidateProperty
|
14
|
-
end
|
15
|
-
|
16
1
|
module Ape
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
if driver.loadSchema(InputSource.new(StringReader.new(schema)))
|
38
|
-
begin
|
39
|
-
if !driver.validate(InputSource.new(StringReader.new(text)))
|
40
|
-
error = schemaError.toString
|
2
|
+
require 'rexml/document'
|
3
|
+
class ValidationError < StandardError ; end
|
4
|
+
|
5
|
+
class Validator
|
6
|
+
require File.dirname(__FILE__) + '/validator_dsl.rb'
|
7
|
+
include Ape::ValidatorDsl
|
8
|
+
include Ape::Util
|
9
|
+
|
10
|
+
attr_accessor :reporter, :authent
|
11
|
+
|
12
|
+
def self.custom_validators(reporter, authent)
|
13
|
+
validators = []
|
14
|
+
Dir[Ape.home + '/validators/*.rb'].each do |v|
|
15
|
+
require v
|
16
|
+
class_name = v.gsub(/(.+\/validators\/)(.+)(.rb)/, '\2').gsub(/(^|_)(.)/) { $2.upcase }
|
17
|
+
validator = eval("#{class_name}.new", binding, __FILE__, __LINE__)
|
18
|
+
if validator.enabled?
|
19
|
+
validator.reporter = reporter
|
20
|
+
validator.authent = authent
|
21
|
+
validators << validator
|
41
22
|
end
|
42
|
-
rescue org.xml.sax.SAXParseException
|
43
|
-
error = $!.to_s.sub(/\n.*$/, '')
|
44
23
|
end
|
45
|
-
|
46
|
-
error = schemaError.toString
|
24
|
+
validators
|
47
25
|
end
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
# tell jing what name I'd like to call the InputSource
|
56
|
-
ape.error "#{name} failed schema validation:\n" + error.gsub('(unknown file):', 'Line ')
|
57
|
-
false
|
26
|
+
|
27
|
+
def self.instance(key, reporter, authent = nil)
|
28
|
+
validator = resolve_plugin(key, 'validators', 'validator')
|
29
|
+
raise ValidationError, "Unknown validator #{key}" unless validator
|
30
|
+
validator.reporter = reporter
|
31
|
+
validator.authent = authent
|
32
|
+
validator
|
58
33
|
end
|
34
|
+
|
35
|
+
=begin
|
36
|
+
Each validator implements its own bussiness logic. This method is executed by the main script
|
37
|
+
in order to assure that some aspect of atomPub implementation is correct
|
38
|
+
=end
|
39
|
+
def validate(opts = {})
|
40
|
+
raise ValidationError, "superclass doesn't implement this method"
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
# Fetch a feed and look up an entry by ID in it
|
45
|
+
def find_entry(feed_uri, name, id, report=false)
|
46
|
+
entries = Feed.read(feed_uri, name, reporter, report)
|
47
|
+
entries.each do |from_feed|
|
48
|
+
return from_feed if id == from_feed.child_content('id')
|
49
|
+
end
|
59
50
|
|
51
|
+
return "Couldn't find id #{id} in feed #{feed_uri}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def delete_entry(entry, name = nil)
|
55
|
+
link = entry.link('edit', self)
|
56
|
+
unless link
|
57
|
+
reporter.error(self, "Can't delete entry without edit link")
|
58
|
+
return false
|
59
|
+
end
|
60
|
+
deleter = Deleter.new(link, @authent)
|
61
|
+
worked = deleter.delete
|
62
|
+
|
63
|
+
reporter.save_dialog(name, deleter) if name
|
64
|
+
if worked
|
65
|
+
reporter.success(self, "Entry deletion reported success.", name)
|
66
|
+
else
|
67
|
+
reporter.error(self, "Couldn't delete the entry: " + deleter.last_error, name)
|
68
|
+
end
|
69
|
+
return worked
|
70
|
+
end
|
71
|
+
|
72
|
+
def method_missing(name, *args)
|
73
|
+
if (name == :enabled?)
|
74
|
+
new_method = self.class.send(:define_method, 'enabled?') do
|
75
|
+
return true
|
76
|
+
end
|
77
|
+
elsif (name == :deterministic?)
|
78
|
+
new_method = self.class.send(:define_method, 'deterministic?') do
|
79
|
+
return false
|
80
|
+
end
|
81
|
+
elsif (name == :manifest)
|
82
|
+
new_method = self.class.send(:define_method, 'manifest') do
|
83
|
+
return []
|
84
|
+
end
|
85
|
+
else
|
86
|
+
super
|
87
|
+
end
|
88
|
+
new_method.call(args) if new_method
|
89
|
+
end
|
90
|
+
|
60
91
|
end
|
61
|
-
|
92
|
+
Dir[File.dirname(__FILE__) + '/validators/*.rb'].each { |l| require l }
|
62
93
|
end
|
63
|
-
end
|
64
|
-
|
65
|
-
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Ape
|
2
|
+
module ValidatorDsl
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def enabled
|
9
|
+
define_method('enabled?') do
|
10
|
+
return true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def disabled
|
15
|
+
define_method('enabled?') do
|
16
|
+
return false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def deterministic
|
21
|
+
define_method('deterministic?') do
|
22
|
+
return true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def nondeterministic
|
27
|
+
define_method('deterministic?') do
|
28
|
+
return false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def requires_presence_of(*args)
|
33
|
+
define_method('manifest') do
|
34
|
+
return args
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
module Ape
|
2
|
+
class EntryPostsValidator < Validator
|
3
|
+
disabled
|
4
|
+
requires_presence_of :entry_collection
|
5
|
+
|
6
|
+
def validate(opts = {})
|
7
|
+
entry_collection = opts[:entry_collection]
|
8
|
+
reporter.info(self, "Will use collection '#{entry_collection.title}' for entry creation.")
|
9
|
+
|
10
|
+
collection_uri = entry_collection.href
|
11
|
+
entries = Feed.read(collection_uri, 'Entry collection', reporter)
|
12
|
+
|
13
|
+
# * List the current entries, remember which IDs we've seen
|
14
|
+
reporter.info(self, "TESTING: Entry-posting basics.")
|
15
|
+
ids = []
|
16
|
+
unless entries.empty?
|
17
|
+
reporter.start_list(self, "Now in the Entries feed")
|
18
|
+
entries.each do |entry|
|
19
|
+
reporter.list_item(entry.summarize)
|
20
|
+
ids << entry.child_content('id')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Setting up to post a new entry
|
25
|
+
poster = Poster.new(collection_uri, @authent)
|
26
|
+
if poster.last_error
|
27
|
+
reporter.error(self, "Unacceptable URI for '#{entry_collection.title}' collection: " +
|
28
|
+
poster.last_error)
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
my_entry = Entry.new(Samples.basic_entry)
|
33
|
+
|
34
|
+
# ask it to use this in the URI
|
35
|
+
slug_num = rand(100000)
|
36
|
+
slug = "ape-#{slug_num}"
|
37
|
+
slug_re = %r{ape.?#{slug_num}}
|
38
|
+
poster.set_header('Slug', slug)
|
39
|
+
|
40
|
+
# add some categories to the entry, and remember which
|
41
|
+
@cats = Categories.add_cats(my_entry, entry_collection, @authent, reporter)
|
42
|
+
|
43
|
+
# * OK, post it
|
44
|
+
worked = poster.post(Names::AtomEntryMediaType, my_entry.to_s)
|
45
|
+
name = 'Posting new entry'
|
46
|
+
reporter.save_dialog(name, poster)
|
47
|
+
if !worked
|
48
|
+
reporter.error(self, "Can't POST new entry: #{poster.last_error}", name)
|
49
|
+
return
|
50
|
+
end
|
51
|
+
|
52
|
+
location = poster.header('Location')
|
53
|
+
unless location
|
54
|
+
reporter.error(self, "No Location header upon POST creation", name)
|
55
|
+
return
|
56
|
+
end
|
57
|
+
reporter.success(self, "Posting of new entry to the Entries collection " +
|
58
|
+
"reported success, Location: #{location}", name)
|
59
|
+
|
60
|
+
reporter.info(self, "Examining the new entry as returned in the POST response")
|
61
|
+
check_new_entry(my_entry, poster.entry, "Returned entry") if poster.entry
|
62
|
+
|
63
|
+
# * See if the Location uri can be retrieved, and check its consistency
|
64
|
+
name = "Retrieval of newly created entry"
|
65
|
+
new_entry = check_resource(location, name, Names::AtomMediaType)
|
66
|
+
return unless new_entry
|
67
|
+
|
68
|
+
# Grab its etag
|
69
|
+
etag = new_entry.header 'etag'
|
70
|
+
|
71
|
+
reporter.info(self, "Examining the new entry as retrieved using Location header in POST response:")
|
72
|
+
|
73
|
+
begin
|
74
|
+
new_entry = Entry.new(new_entry.body, location)
|
75
|
+
rescue REXML::ParseException
|
76
|
+
prob = $!.to_s.gsub(/\n/, '<br/>')
|
77
|
+
reporter.error(self, "New entry is not well-formed: #{prob}")
|
78
|
+
return
|
79
|
+
end
|
80
|
+
|
81
|
+
# * See if the slug was used
|
82
|
+
slug_used = false
|
83
|
+
new_entry.alt_links.each do |a|
|
84
|
+
href = a.attributes['href']
|
85
|
+
if href && href.index(slug_re)
|
86
|
+
slug_used = true
|
87
|
+
end
|
88
|
+
end
|
89
|
+
if slug_used
|
90
|
+
reporter.success(self, "Client-provided slug '#{slug}' was used in server-generated URI.")
|
91
|
+
else
|
92
|
+
reporter.warning(self, "Client-provided slug '#{slug}' not used in server-generated URI.")
|
93
|
+
end
|
94
|
+
|
95
|
+
check_new_entry(my_entry, new_entry, "Retrieved entry")
|
96
|
+
|
97
|
+
entry_id = new_entry.child_content('id')
|
98
|
+
|
99
|
+
# * fetch the feed again and check that version
|
100
|
+
from_feed = find_entry(collection_uri, "entry collection", entry_id)
|
101
|
+
if from_feed.class == String
|
102
|
+
reporter.success(self, "About to check #{collection_uri}")
|
103
|
+
Feed.read(collection_uri, "Can't find entry in collection", reporter)
|
104
|
+
reporter.error(self, "New entry didn't show up in the collections feed.")
|
105
|
+
return
|
106
|
+
end
|
107
|
+
|
108
|
+
reporter.info(self, "Examining the new entry as it appears in the collection feed:")
|
109
|
+
|
110
|
+
# * Check the entry from the feed
|
111
|
+
check_new_entry(my_entry, from_feed, "Entry from collection feed")
|
112
|
+
|
113
|
+
edit_uri = new_entry.link('edit', self)
|
114
|
+
if !edit_uri
|
115
|
+
reporter.error(self, "Entry from Location header has no edit link.")
|
116
|
+
return
|
117
|
+
end
|
118
|
+
|
119
|
+
# * Update the entry, see if the update took
|
120
|
+
name = 'In-place update with put'
|
121
|
+
putter = Putter.new(edit_uri, @authent)
|
122
|
+
|
123
|
+
# Conditional PUT if an etag
|
124
|
+
putter.set_header('If-Match', etag) if etag
|
125
|
+
|
126
|
+
new_title = "Let’s all do the Ape!"
|
127
|
+
new_text = Samples.retitled_entry(new_title, entry_id)
|
128
|
+
response = putter.put(Names::AtomEntryMediaType, new_text)
|
129
|
+
reporter.save_dialog(name, putter)
|
130
|
+
|
131
|
+
if response
|
132
|
+
reporter.success(self, "Update of new entry reported success.", name)
|
133
|
+
from_feed = find_entry(collection_uri, "entry collection", entry_id)
|
134
|
+
if from_feed.class == String
|
135
|
+
check_resource(collection_uri, "Check collection after lost update")
|
136
|
+
reporter.error(self, "Updated entry ID #{entry_id} not found in entries collection.")
|
137
|
+
return
|
138
|
+
end
|
139
|
+
if from_feed.child_content('title') == new_title
|
140
|
+
reporter.success(self, "Title of new entry successfully updated.")
|
141
|
+
else
|
142
|
+
reporter.warning(self, "After PUT update of title, Expected " +
|
143
|
+
"'#{new_title}', but saw '#{from_feed.child_content('title')}'")
|
144
|
+
end
|
145
|
+
else
|
146
|
+
reporter.warning(self,"Can't update new entry with PUT: #{putter.last_error}", name)
|
147
|
+
end
|
148
|
+
|
149
|
+
# the edit-uri might have changed
|
150
|
+
return unless delete_entry(from_feed, 'New Entry deletion')
|
151
|
+
|
152
|
+
# See if it's gone from the feed
|
153
|
+
still_there = find_entry(collection_uri, "entry collection", entry_id)
|
154
|
+
if still_there.class != String
|
155
|
+
reporter.error(self, "Entry is still in collection post-deletion.")
|
156
|
+
else
|
157
|
+
reporter.success(self, "Entry not found in feed after deletion.")
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
def check_new_entry(as_posted, new_entry, desc)
|
163
|
+
|
164
|
+
if compare_entries(as_posted, new_entry, "entry as posted", desc)
|
165
|
+
reporter.success(self, "#{desc} is consistent with posted entry.")
|
166
|
+
end
|
167
|
+
|
168
|
+
# * See if the categories we sent made it in
|
169
|
+
cat_probs = false
|
170
|
+
@cats.each do |cat|
|
171
|
+
if !new_entry.has_cat(cat)
|
172
|
+
cat_probs = true
|
173
|
+
reporter.warning(self, "Provided category not in #{desc}: #{cat}")
|
174
|
+
end
|
175
|
+
end
|
176
|
+
reporter.success(self, "Provided categories included in #{desc}.") unless cat_probs
|
177
|
+
|
178
|
+
# * See if the dc:subject survived
|
179
|
+
dc_subject = new_entry.child_content(Samples.foreign_child, Samples.foreign_namespace)
|
180
|
+
if dc_subject
|
181
|
+
if dc_subject == Samples.foreign_child_content
|
182
|
+
reporter.success(self, "Server preserved foreign markup in #{desc}.")
|
183
|
+
else
|
184
|
+
reporter.warning(self, "Server altered content of foreign markup in #{desc}.")
|
185
|
+
end
|
186
|
+
else
|
187
|
+
reporter.warning(self, "Server discarded foreign markup in #{desc}.")
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def compare_entries(e1, e2, e1Name, e2Name)
|
192
|
+
problems = 0
|
193
|
+
[ 'title', 'summary', 'content' ].each do |field|
|
194
|
+
problems += 1 if compare1(e1, e2, e1Name, e2Name, field)
|
195
|
+
end
|
196
|
+
return problems == 0
|
197
|
+
end
|
198
|
+
|
199
|
+
def compare1(e1, e2, e1Name, e2Name, field)
|
200
|
+
c1 = e1.child_content(field)
|
201
|
+
c2 = e2.child_content(field)
|
202
|
+
if c1 != c2
|
203
|
+
problem = true
|
204
|
+
if c1 == nil
|
205
|
+
reporter.warning(self, "'#{field}' absent in #{e1Name}.")
|
206
|
+
elsif c2 == nil
|
207
|
+
reporter.warning(self, "'#{field}' absent in #{e2Name}.")
|
208
|
+
else
|
209
|
+
t1 = e1.child_type(field)
|
210
|
+
t2 = e2.child_type(field)
|
211
|
+
if t1 != t2
|
212
|
+
reporter.warning(self, "'#{field}' has type='#{t1}' " +
|
213
|
+
"in #{e1Name}, type='#{t2}' in #{e2Name}.")
|
214
|
+
else
|
215
|
+
c1 = Escaper.escape(c1)
|
216
|
+
c2 = Escaper.escape(c2)
|
217
|
+
reporter.warning(self, "'#{field}' in #{e1Name} [#{c1}] " +
|
218
|
+
"differs from that in #{e2Name} [#{c2}].")
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
return problem
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Ape
|
2
|
+
class MediaLinkageValidator < Validator
|
3
|
+
disabled
|
4
|
+
requires_presence_of :media_collection
|
5
|
+
|
6
|
+
def validate(opts = {})
|
7
|
+
reporter.info(self, "TESTING: Media collection re-ordering after PUT.")
|
8
|
+
coll = opts[:media_collection]
|
9
|
+
|
10
|
+
# We'll post three mini entries to the collection
|
11
|
+
data = Samples.picture
|
12
|
+
poster = Poster.new(coll.href, @authent)
|
13
|
+
['One', 'Two', 'Three'].each do |num|
|
14
|
+
slug = "Picture #{num}"
|
15
|
+
poster.set_header('Slug', slug)
|
16
|
+
name = "Posting pic #{num}"
|
17
|
+
worked = poster.post('image/jpeg', data)
|
18
|
+
reporter.save_dialog(name, poster)
|
19
|
+
if !worked
|
20
|
+
reporter.error(self, "Can't POST Picture #{num}: #{poster.last_error}", name)
|
21
|
+
return
|
22
|
+
end
|
23
|
+
sleep 2
|
24
|
+
end
|
25
|
+
|
26
|
+
# grab the collection to gather the MLE ids
|
27
|
+
entries = Feed.read(coll.href, 'Pictures from multi-post', reporter)
|
28
|
+
if entries.size < 3
|
29
|
+
reporter.error(self, "Pictures apparently not in collection")
|
30
|
+
return
|
31
|
+
end
|
32
|
+
|
33
|
+
ids = entries.map { |e| e.child_content('id)') }
|
34
|
+
|
35
|
+
# let's update one of them; have to fetch it first to get the ETag
|
36
|
+
two_media = entries[1].link('edit-media')
|
37
|
+
if !two_media
|
38
|
+
reporter.error(self, "Second entry from feed doesn't have an 'edit-media' link.")
|
39
|
+
return
|
40
|
+
end
|
41
|
+
two_resp = check_resource(two_media, 'Fetch image to get ETag', 'image/jpeg')
|
42
|
+
unless two_resp
|
43
|
+
reporter.error(self, "Can't fetch image to get ETag")
|
44
|
+
return
|
45
|
+
end
|
46
|
+
etag = two_resp.header 'etag'
|
47
|
+
|
48
|
+
putter = Putter.new(two_media, @authent)
|
49
|
+
putter.set_header('If-Match', etag)
|
50
|
+
|
51
|
+
name = 'Updating one of three pix with PUT'
|
52
|
+
if putter.put('image/jpeg', data)
|
53
|
+
reporter.success(self, "Update one of newly posted pictures went OK.")
|
54
|
+
else
|
55
|
+
reporter.save_dialog(name, putter)
|
56
|
+
reporter.error(self, "Can't update picture at #{two_media}", name)
|
57
|
+
return
|
58
|
+
end
|
59
|
+
|
60
|
+
# now the order should have changed
|
61
|
+
wanted = [ ids[2], ids[0], ids[1] ]
|
62
|
+
entries = Feed.read(coll.href, 'MLEs post-update', reporter)
|
63
|
+
entries.each do |from_feed|
|
64
|
+
want = wanted.pop
|
65
|
+
unless from_feed.child_content('id').eql?(want)
|
66
|
+
reporter.error(self, "Updating bits failed to re-order link entries in media collection.")
|
67
|
+
return
|
68
|
+
end
|
69
|
+
|
70
|
+
# next to godliness
|
71
|
+
delete_entry(from_feed)
|
72
|
+
|
73
|
+
break if wanted.empty?
|
74
|
+
end
|
75
|
+
reporter.success(self, "Entries correctly ordered after update of multi-post.")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|