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/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
|