offroad 0.0.2 → 0.0.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.
Files changed (61) hide show
  1. data/LICENSE +674 -674
  2. data/README.rdoc +29 -29
  3. data/Rakefile +75 -75
  4. data/TODO +42 -42
  5. data/lib/app/models/offroad/group_state.rb +85 -85
  6. data/lib/app/models/offroad/mirror_info.rb +53 -53
  7. data/lib/app/models/offroad/model_state.rb +36 -36
  8. data/lib/app/models/offroad/received_record_state.rb +115 -115
  9. data/lib/app/models/offroad/sendable_record_state.rb +91 -91
  10. data/lib/app/models/offroad/system_state.rb +33 -33
  11. data/lib/cargo_streamer.rb +222 -222
  12. data/lib/controller_extensions.rb +74 -74
  13. data/lib/exceptions.rb +16 -16
  14. data/lib/migrate/20100512164608_create_offroad_tables.rb +72 -72
  15. data/lib/mirror_data.rb +376 -376
  16. data/lib/model_extensions.rb +378 -377
  17. data/lib/module_funcs.rb +94 -94
  18. data/lib/offroad.rb +41 -41
  19. data/lib/version.rb +3 -3
  20. data/lib/view_helper.rb +7 -7
  21. data/templates/offline.rb +36 -36
  22. data/templates/offline_database.yml +7 -7
  23. data/templates/offroad.yml +6 -6
  24. data/test/app_root/app/controllers/application_controller.rb +2 -2
  25. data/test/app_root/app/controllers/group_controller.rb +28 -28
  26. data/test/app_root/app/models/global_record.rb +10 -10
  27. data/test/app_root/app/models/group.rb +12 -12
  28. data/test/app_root/app/models/group_owned_record.rb +68 -68
  29. data/test/app_root/app/models/guest.rb +7 -7
  30. data/test/app_root/app/models/subrecord.rb +12 -12
  31. data/test/app_root/app/models/unmirrored_record.rb +4 -4
  32. data/test/app_root/app/views/group/download_down_mirror.html.erb +3 -3
  33. data/test/app_root/app/views/group/download_initial_down_mirror.html.erb +3 -3
  34. data/test/app_root/app/views/group/download_up_mirror.html.erb +5 -5
  35. data/test/app_root/app/views/layouts/mirror.html.erb +8 -8
  36. data/test/app_root/config/boot.rb +115 -115
  37. data/test/app_root/config/database-pg.yml +8 -8
  38. data/test/app_root/config/database.yml +5 -5
  39. data/test/app_root/config/environment.rb +24 -24
  40. data/test/app_root/config/environments/test.rb +17 -17
  41. data/test/app_root/config/offroad.yml +6 -6
  42. data/test/app_root/config/routes.rb +4 -4
  43. data/test/app_root/db/migrate/20100529235049_create_tables.rb +64 -64
  44. data/test/app_root/lib/common_hobo.rb +15 -15
  45. data/test/app_root/vendor/plugins/offroad/init.rb +2 -2
  46. data/test/functional/mirror_operations_test.rb +148 -148
  47. data/test/test_helper.rb +453 -453
  48. data/test/unit/app_state_tracking_test.rb +275 -275
  49. data/test/unit/cargo_streamer_test.rb +332 -332
  50. data/test/unit/global_data_test.rb +102 -102
  51. data/test/unit/group_controller_test.rb +152 -152
  52. data/test/unit/group_data_test.rb +442 -435
  53. data/test/unit/group_single_test.rb +136 -136
  54. data/test/unit/hobo_permissions_test.rb +57 -57
  55. data/test/unit/mirror_data_test.rb +1283 -1283
  56. data/test/unit/mirror_info_test.rb +31 -31
  57. data/test/unit/module_funcs_test.rb +37 -37
  58. data/test/unit/pathological_model_test.rb +62 -62
  59. data/test/unit/test_framework_test.rb +86 -86
  60. data/test/unit/unmirrored_data_test.rb +14 -14
  61. metadata +6 -8
@@ -1,332 +1,332 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
-
3
- class CargoStreamerTest < Test::Unit::TestCase
4
- # Based on the pattern found here: http://stackoverflow.com/questions/315850/rails-model-without-database
5
- class TestModel < ActiveRecord::Base
6
- self.abstract_class = true
7
-
8
- def self.columns
9
- @columns ||= []
10
- end
11
-
12
- columns << ActiveRecord::ConnectionAdapters::Column.new("id", nil, "integer", true)
13
- columns << ActiveRecord::ConnectionAdapters::Column.new("data", "", "string", false)
14
-
15
- has_one(:fake_association)
16
- attr_accessor :fake_association
17
-
18
- validates_presence_of :data
19
-
20
- @@safety_switch = true
21
-
22
- def self.safe_to_load_from_cargo_stream?
23
- @@safety_switch
24
- end
25
-
26
- def self.set_safe
27
- @@safety_switch = true
28
- end
29
-
30
- def self.set_unsafe
31
- @@safety_switch = false
32
- end
33
- end
34
-
35
- def generate_cargo_string(hash, skip_validation = false)
36
- return StringIO.open do |sio|
37
- writer = Offroad::CargoStreamer.new(sio, "w")
38
- hash.each do |key, arr|
39
- arr.each do |elem|
40
- writer.write_cargo_section(key, elem, :skip_validation => skip_validation)
41
- end
42
- end
43
-
44
- sio.string
45
- end
46
- end
47
-
48
- def retrieve_cargo_from_string(str)
49
- hash = {}
50
-
51
- reader = Offroad::CargoStreamer.new(str, "r")
52
- reader.cargo_section_names.each do |key|
53
- hash[key] = []
54
- reader.each_cargo_section(key) do |elem|
55
- hash[key] << elem
56
- end
57
- end
58
-
59
- return hash
60
- end
61
-
62
- def round_trip(hash = {})
63
- retrieve_cargo_from_string(generate_cargo_string(hash))
64
- end
65
-
66
- # Asserts content equality between two hashes of arrays of arrays of records ("haar"s)
67
- # Cannot just do new_hash == hash, because ActiveRecord#== always false when comparing two unsaved records.
68
- def assert_haar_equality(first_hash, second_hash)
69
- assert_nothing_raised do
70
- # Assert that they are subsets of each other
71
- [[first_hash, second_hash], [second_hash, first_hash]].each do |hash_a, hash_b|
72
- hash_a.each do |key, arr|
73
- arr.each_with_index do |subarr, i|
74
- subarr.each_with_index do |rec, j|
75
- rec.attributes.each_pair do |attr_key, attr_value|
76
- raise "Mismatch" unless attr_value == hash_b[key][i][j].attributes[attr_key]
77
- end
78
- end
79
- end
80
- end
81
- end
82
- end
83
- end
84
-
85
- def assert_round_trip_equality(hash = {})
86
- assert_haar_equality(hash, round_trip(hash))
87
- end
88
-
89
- def test_rec(str)
90
- TestModel.new(:data => str)
91
- end
92
-
93
- agnostic_test "can encode and retrieve a model instances in an array" do
94
- assert_round_trip_equality "test" => [[test_rec("A"), test_rec("B")]]
95
- end
96
-
97
- agnostic_test "can encode and retrieve model instances with first-level association data" do
98
- r1 = test_rec("A")
99
- r1.fake_association = test_rec("B")
100
- r2 = test_rec("XYZ") # Making sure we can also encode a model instance that doesn't have the assoc data
101
-
102
- str = StringIO.open do |sio|
103
- writer = Offroad::CargoStreamer.new(sio, "w")
104
- writer.write_cargo_section("abc", [r1, r2], :include => [:fake_association])
105
- sio.string
106
- end
107
-
108
- decoded_rec = StringIO.open(str) do |sio|
109
- cs = Offroad::CargoStreamer.new(sio, "r")
110
- cs.first_cargo_section("abc")[0]
111
- end
112
-
113
- assert_equal "B", decoded_rec.fake_association.data
114
- end
115
-
116
- agnostic_test "encoded models do not lose their id" do
117
- rec = test_rec("ABC")
118
- rec.id = 45
119
- decoded = round_trip "test" => [[rec]]
120
- assert_equal 45, decoded["test"][0][0].id
121
- end
122
-
123
- agnostic_test "cannot encode and retrieve non-model data" do
124
- assert_raise Offroad::CargoStreamerError do
125
- generate_cargo_string "a" => [[1]]
126
- end
127
- end
128
-
129
- agnostic_test "cannot encode a model that is not in an array" do
130
- assert_raise Offroad::CargoStreamerError do
131
- # This is not "in an array" for CargoStreamer; look at how generate_cargo_string is implemented
132
- generate_cargo_string "a" => [test_rec("Test")]
133
- end
134
- end
135
-
136
- agnostic_test "can decode cargo data even if there is other stuff around it" do
137
- test_hash = {"foo bar narf bork" => [[test_rec("Test")]]}
138
- str = "BLAH BLAH BLAH" + generate_cargo_string(test_hash) + "BAR BAR BAR"
139
- assert_haar_equality test_hash, retrieve_cargo_from_string(str)
140
- end
141
-
142
- agnostic_test "can correctly identify the names of the cargo sections" do
143
- test_hash = {"abc" => [[test_rec("A")]], "xyz" => [[test_rec("X")]]}
144
- cs = Offroad::CargoStreamer.new(generate_cargo_string(test_hash), "r")
145
- assert_equal test_hash.keys, cs.cargo_section_names
146
- assert cs.has_cargo_named?("abc")
147
- assert_equal false, cs.has_cargo_named?("foobar")
148
- end
149
-
150
- agnostic_test "can create and retrieve multiple ordered cargo sections with the same name" do
151
- test_data = [[test_rec("a"), test_rec("b")], [test_rec("c"), test_rec("d")], [test_rec("e"), test_rec("f")]]
152
-
153
- str = StringIO.open do |sio|
154
- cs = Offroad::CargoStreamer.new(sio, "w")
155
- test_data.each do |dat|
156
- cs.write_cargo_section("xyz", dat)
157
- end
158
- sio.string
159
- end
160
-
161
- result_data = []
162
- cs = Offroad::CargoStreamer.new(str, "r")
163
- cs.each_cargo_section "xyz" do |dat|
164
- result_data << dat
165
- end
166
-
167
- assert_haar_equality({"test" => test_data}, {"test" => result_data})
168
- end
169
-
170
- agnostic_test "can use first_cargo_section to get only the first section with a given name" do
171
- result = StringIO.open do |sio|
172
- cs = Offroad::CargoStreamer.new(sio, "w")
173
- for i in 1..3 do
174
- cs.write_cargo_section("testing", [test_rec("item number #{i}")])
175
- end
176
- sio.string
177
- end
178
-
179
- cs = Offroad::CargoStreamer.new(result, "r")
180
- assert_equal test_rec("item number 1").attributes, cs.first_cargo_section("testing")[0].attributes
181
- assert_equal nil, cs.first_cargo_section("no-such-section")
182
- end
183
-
184
- agnostic_test "can use first_cargo_element to get the first element of the first section with a given name" do
185
- result = StringIO.open do |sio|
186
- cs = Offroad::CargoStreamer.new(sio, "w")
187
- [10, 20, 30].each do |i|
188
- cs.write_cargo_section("testing", [test_rec("item number #{i}"), test_rec("item number #{i+1}")])
189
- end
190
- sio.string
191
- end
192
-
193
- cs = Offroad::CargoStreamer.new(result, "r")
194
- assert_equal test_rec("item number 10").attributes, cs.first_cargo_element("testing").attributes
195
- assert_equal nil, cs.first_cargo_element("no-such-section")
196
- end
197
-
198
- agnostic_test "can use :human_readable to include a string version of a record" do
199
- test_str = "ABCD\n123"
200
- rec = test_rec(test_str)
201
-
202
- result = StringIO.open do |sio|
203
- cs = Offroad::CargoStreamer.new(sio, "w")
204
- cs.write_cargo_section("test", [rec], :human_readable => false)
205
- sio.string
206
- end
207
- assert_equal false, result.include?(test_str)
208
-
209
- result = StringIO.open do |sio|
210
- cs = Offroad::CargoStreamer.new(sio, "w")
211
- cs.write_cargo_section("test", [rec], :human_readable => true)
212
- sio.string
213
- end
214
- assert result.include?(test_str)
215
- end
216
-
217
- agnostic_test "uses an md5 fingerprint to detect corruption" do
218
- str = generate_cargo_string "test" => [[test_rec("abc")]]
219
-
220
- md5sum = nil
221
- if str =~ /\b([0-9a-f]{32})\b/
222
- md5sum = $1
223
- else
224
- flunk "Unable to find an md5sum in the generated string"
225
- end
226
- assert_raise Offroad::CargoStreamerError, "Changing fingerprint causes exception to be raised" do
227
- retrieve_cargo_from_string(str.gsub md5sum, "a"*md5sum.size)
228
- end
229
-
230
- assert_raise Offroad::CargoStreamerError, "Changing base64 content causes exception to be raised" do
231
- # This is somewhat of an implementation-dependent test; I checked manually that the data has these strings.
232
- # It's safe, though, as changing the implementation should cause false neg, not false pos.
233
- retrieve_cargo_from_string(str.sub("X", "x"))
234
- end
235
- end
236
-
237
- agnostic_test "modes r and w work, other modes do not" do
238
- assert_nothing_raised "Mode r works" do
239
- Offroad::CargoStreamer.new(StringIO.new(), "r")
240
- end
241
-
242
- assert_nothing_raised "Mode w works" do
243
- Offroad::CargoStreamer.new(StringIO.new(), "w")
244
- end
245
-
246
- assert_raise Offroad::CargoStreamerError, "Mode a doesn't work" do
247
- Offroad::CargoStreamer.new(StringIO.new(), "a")
248
- end
249
- end
250
-
251
- agnostic_test "cannot write cargo in read mode" do
252
- assert_raise Offroad::CargoStreamerError do
253
- cs = Offroad::CargoStreamer.new(StringIO.new, "r")
254
- cs.write_cargo_section("test", [test_rec("test")])
255
- end
256
- end
257
-
258
- agnostic_test "cannot use invalid cargo section names" do
259
- cs = Offroad::CargoStreamer.new(StringIO.new, "w")
260
-
261
- assert_raise Offroad::CargoStreamerError, "Expect exception for symbol cargo name" do
262
- cs.write_cargo_section(:test, [test_rec("test")])
263
- end
264
-
265
- assert_raise Offroad::CargoStreamerError, "Expect exception for cargo name that's bad in HTML comments" do
266
- cs.write_cargo_section("whatever--foobar", [test_rec("test")])
267
- end
268
-
269
- assert_raise Offroad::CargoStreamerError, "Expect exception for cargo name that's multiline" do
270
- cs.write_cargo_section("whatever\nfoobar", [test_rec("test")])
271
- end
272
- end
273
-
274
- agnostic_test "cannot encode invalid records except with :skip_validation option" do
275
- rec = TestModel.new() # Nothing set to the required "data" field
276
- assert_equal false, rec.valid?
277
- assert_raise Offroad::CargoStreamerError do
278
- generate_cargo_string "foo" => [[rec]]
279
- end
280
- assert_nothing_raised Offroad::CargoStreamerError do
281
- generate_cargo_string({"foo" => [[rec]]}, true)
282
- end
283
- rec.data = "Something"
284
- assert_nothing_raised do
285
- generate_cargo_string "foo" => [[rec]]
286
- end
287
- end
288
-
289
- agnostic_test "cannot encode a non-safe model class" do
290
- begin
291
- TestModel.set_safe
292
- assert_nothing_raised do
293
- str = generate_cargo_string "test" => [[test_rec("ABC")]]
294
- end
295
- TestModel.set_unsafe
296
- assert_raise Offroad::CargoStreamerError do
297
- str = generate_cargo_string "test" => [[test_rec("ABC")]]
298
- end
299
- ensure
300
- TestModel.set_safe
301
- end
302
- end
303
-
304
- agnostic_test "cannot trick cargo streamer into decoding a non-safe model class" do
305
- begin
306
- TestModel.set_safe
307
- str = generate_cargo_string "test" => [[test_rec("Stuff")]]
308
-
309
- assert_nothing_raised do
310
- retrieve_cargo_from_string(str)
311
- end
312
- TestModel.set_unsafe
313
- assert_raise Offroad::CargoStreamerError do
314
- retrieve_cargo_from_string(str)
315
- end
316
- ensure
317
- TestModel.set_safe
318
- end
319
- end
320
-
321
- agnostic_test "cargo streamer can read directly from a passed in string" do
322
- str = generate_cargo_string("test" => [[test_rec("abc")]])
323
- cs = Offroad::CargoStreamer.new(str, "r")
324
- assert cs.has_cargo_named?("test")
325
- end
326
-
327
- agnostic_test "cannot have cargo streamer write to a passed in string" do
328
- assert_raise Offroad::CargoStreamerError do
329
- Offroad::CargoStreamer.new("", "w")
330
- end
331
- end
332
- end
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class CargoStreamerTest < Test::Unit::TestCase
4
+ # Based on the pattern found here: http://stackoverflow.com/questions/315850/rails-model-without-database
5
+ class TestModel < ActiveRecord::Base
6
+ self.abstract_class = true
7
+
8
+ def self.columns
9
+ @columns ||= []
10
+ end
11
+
12
+ columns << ActiveRecord::ConnectionAdapters::Column.new("id", nil, "integer", true)
13
+ columns << ActiveRecord::ConnectionAdapters::Column.new("data", "", "string", false)
14
+
15
+ has_one(:fake_association)
16
+ attr_accessor :fake_association
17
+
18
+ validates_presence_of :data
19
+
20
+ @@safety_switch = true
21
+
22
+ def self.safe_to_load_from_cargo_stream?
23
+ @@safety_switch
24
+ end
25
+
26
+ def self.set_safe
27
+ @@safety_switch = true
28
+ end
29
+
30
+ def self.set_unsafe
31
+ @@safety_switch = false
32
+ end
33
+ end
34
+
35
+ def generate_cargo_string(hash, skip_validation = false)
36
+ return StringIO.open do |sio|
37
+ writer = Offroad::CargoStreamer.new(sio, "w")
38
+ hash.each do |key, arr|
39
+ arr.each do |elem|
40
+ writer.write_cargo_section(key, elem, :skip_validation => skip_validation)
41
+ end
42
+ end
43
+
44
+ sio.string
45
+ end
46
+ end
47
+
48
+ def retrieve_cargo_from_string(str)
49
+ hash = {}
50
+
51
+ reader = Offroad::CargoStreamer.new(str, "r")
52
+ reader.cargo_section_names.each do |key|
53
+ hash[key] = []
54
+ reader.each_cargo_section(key) do |elem|
55
+ hash[key] << elem
56
+ end
57
+ end
58
+
59
+ return hash
60
+ end
61
+
62
+ def round_trip(hash = {})
63
+ retrieve_cargo_from_string(generate_cargo_string(hash))
64
+ end
65
+
66
+ # Asserts content equality between two hashes of arrays of arrays of records ("haar"s)
67
+ # Cannot just do new_hash == hash, because ActiveRecord#== always false when comparing two unsaved records.
68
+ def assert_haar_equality(first_hash, second_hash)
69
+ assert_nothing_raised do
70
+ # Assert that they are subsets of each other
71
+ [[first_hash, second_hash], [second_hash, first_hash]].each do |hash_a, hash_b|
72
+ hash_a.each do |key, arr|
73
+ arr.each_with_index do |subarr, i|
74
+ subarr.each_with_index do |rec, j|
75
+ rec.attributes.each_pair do |attr_key, attr_value|
76
+ raise "Mismatch" unless attr_value == hash_b[key][i][j].attributes[attr_key]
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ def assert_round_trip_equality(hash = {})
86
+ assert_haar_equality(hash, round_trip(hash))
87
+ end
88
+
89
+ def test_rec(str)
90
+ TestModel.new(:data => str)
91
+ end
92
+
93
+ agnostic_test "can encode and retrieve a model instances in an array" do
94
+ assert_round_trip_equality "test" => [[test_rec("A"), test_rec("B")]]
95
+ end
96
+
97
+ agnostic_test "can encode and retrieve model instances with first-level association data" do
98
+ r1 = test_rec("A")
99
+ r1.fake_association = test_rec("B")
100
+ r2 = test_rec("XYZ") # Making sure we can also encode a model instance that doesn't have the assoc data
101
+
102
+ str = StringIO.open do |sio|
103
+ writer = Offroad::CargoStreamer.new(sio, "w")
104
+ writer.write_cargo_section("abc", [r1, r2], :include => [:fake_association])
105
+ sio.string
106
+ end
107
+
108
+ decoded_rec = StringIO.open(str) do |sio|
109
+ cs = Offroad::CargoStreamer.new(sio, "r")
110
+ cs.first_cargo_section("abc")[0]
111
+ end
112
+
113
+ assert_equal "B", decoded_rec.fake_association.data
114
+ end
115
+
116
+ agnostic_test "encoded models do not lose their id" do
117
+ rec = test_rec("ABC")
118
+ rec.id = 45
119
+ decoded = round_trip "test" => [[rec]]
120
+ assert_equal 45, decoded["test"][0][0].id
121
+ end
122
+
123
+ agnostic_test "cannot encode and retrieve non-model data" do
124
+ assert_raise Offroad::CargoStreamerError do
125
+ generate_cargo_string "a" => [[1]]
126
+ end
127
+ end
128
+
129
+ agnostic_test "cannot encode a model that is not in an array" do
130
+ assert_raise Offroad::CargoStreamerError do
131
+ # This is not "in an array" for CargoStreamer; look at how generate_cargo_string is implemented
132
+ generate_cargo_string "a" => [test_rec("Test")]
133
+ end
134
+ end
135
+
136
+ agnostic_test "can decode cargo data even if there is other stuff around it" do
137
+ test_hash = {"foo bar narf bork" => [[test_rec("Test")]]}
138
+ str = "BLAH BLAH BLAH" + generate_cargo_string(test_hash) + "BAR BAR BAR"
139
+ assert_haar_equality test_hash, retrieve_cargo_from_string(str)
140
+ end
141
+
142
+ agnostic_test "can correctly identify the names of the cargo sections" do
143
+ test_hash = {"abc" => [[test_rec("A")]], "xyz" => [[test_rec("X")]]}
144
+ cs = Offroad::CargoStreamer.new(generate_cargo_string(test_hash), "r")
145
+ assert_equal test_hash.keys, cs.cargo_section_names
146
+ assert cs.has_cargo_named?("abc")
147
+ assert_equal false, cs.has_cargo_named?("foobar")
148
+ end
149
+
150
+ agnostic_test "can create and retrieve multiple ordered cargo sections with the same name" do
151
+ test_data = [[test_rec("a"), test_rec("b")], [test_rec("c"), test_rec("d")], [test_rec("e"), test_rec("f")]]
152
+
153
+ str = StringIO.open do |sio|
154
+ cs = Offroad::CargoStreamer.new(sio, "w")
155
+ test_data.each do |dat|
156
+ cs.write_cargo_section("xyz", dat)
157
+ end
158
+ sio.string
159
+ end
160
+
161
+ result_data = []
162
+ cs = Offroad::CargoStreamer.new(str, "r")
163
+ cs.each_cargo_section "xyz" do |dat|
164
+ result_data << dat
165
+ end
166
+
167
+ assert_haar_equality({"test" => test_data}, {"test" => result_data})
168
+ end
169
+
170
+ agnostic_test "can use first_cargo_section to get only the first section with a given name" do
171
+ result = StringIO.open do |sio|
172
+ cs = Offroad::CargoStreamer.new(sio, "w")
173
+ for i in 1..3 do
174
+ cs.write_cargo_section("testing", [test_rec("item number #{i}")])
175
+ end
176
+ sio.string
177
+ end
178
+
179
+ cs = Offroad::CargoStreamer.new(result, "r")
180
+ assert_equal test_rec("item number 1").attributes, cs.first_cargo_section("testing")[0].attributes
181
+ assert_equal nil, cs.first_cargo_section("no-such-section")
182
+ end
183
+
184
+ agnostic_test "can use first_cargo_element to get the first element of the first section with a given name" do
185
+ result = StringIO.open do |sio|
186
+ cs = Offroad::CargoStreamer.new(sio, "w")
187
+ [10, 20, 30].each do |i|
188
+ cs.write_cargo_section("testing", [test_rec("item number #{i}"), test_rec("item number #{i+1}")])
189
+ end
190
+ sio.string
191
+ end
192
+
193
+ cs = Offroad::CargoStreamer.new(result, "r")
194
+ assert_equal test_rec("item number 10").attributes, cs.first_cargo_element("testing").attributes
195
+ assert_equal nil, cs.first_cargo_element("no-such-section")
196
+ end
197
+
198
+ agnostic_test "can use :human_readable to include a string version of a record" do
199
+ test_str = "ABCD\n123"
200
+ rec = test_rec(test_str)
201
+
202
+ result = StringIO.open do |sio|
203
+ cs = Offroad::CargoStreamer.new(sio, "w")
204
+ cs.write_cargo_section("test", [rec], :human_readable => false)
205
+ sio.string
206
+ end
207
+ assert_equal false, result.include?(test_str)
208
+
209
+ result = StringIO.open do |sio|
210
+ cs = Offroad::CargoStreamer.new(sio, "w")
211
+ cs.write_cargo_section("test", [rec], :human_readable => true)
212
+ sio.string
213
+ end
214
+ assert result.include?(test_str)
215
+ end
216
+
217
+ agnostic_test "uses an md5 fingerprint to detect corruption" do
218
+ str = generate_cargo_string "test" => [[test_rec("abc")]]
219
+
220
+ md5sum = nil
221
+ if str =~ /\b([0-9a-f]{32})\b/
222
+ md5sum = $1
223
+ else
224
+ flunk "Unable to find an md5sum in the generated string"
225
+ end
226
+ assert_raise Offroad::CargoStreamerError, "Changing fingerprint causes exception to be raised" do
227
+ retrieve_cargo_from_string(str.gsub md5sum, "a"*md5sum.size)
228
+ end
229
+
230
+ assert_raise Offroad::CargoStreamerError, "Changing base64 content causes exception to be raised" do
231
+ # This is somewhat of an implementation-dependent test; I checked manually that the data has these strings.
232
+ # It's safe, though, as changing the implementation should cause false neg, not false pos.
233
+ retrieve_cargo_from_string(str.sub("X", "x"))
234
+ end
235
+ end
236
+
237
+ agnostic_test "modes r and w work, other modes do not" do
238
+ assert_nothing_raised "Mode r works" do
239
+ Offroad::CargoStreamer.new(StringIO.new(), "r")
240
+ end
241
+
242
+ assert_nothing_raised "Mode w works" do
243
+ Offroad::CargoStreamer.new(StringIO.new(), "w")
244
+ end
245
+
246
+ assert_raise Offroad::CargoStreamerError, "Mode a doesn't work" do
247
+ Offroad::CargoStreamer.new(StringIO.new(), "a")
248
+ end
249
+ end
250
+
251
+ agnostic_test "cannot write cargo in read mode" do
252
+ assert_raise Offroad::CargoStreamerError do
253
+ cs = Offroad::CargoStreamer.new(StringIO.new, "r")
254
+ cs.write_cargo_section("test", [test_rec("test")])
255
+ end
256
+ end
257
+
258
+ agnostic_test "cannot use invalid cargo section names" do
259
+ cs = Offroad::CargoStreamer.new(StringIO.new, "w")
260
+
261
+ assert_raise Offroad::CargoStreamerError, "Expect exception for symbol cargo name" do
262
+ cs.write_cargo_section(:test, [test_rec("test")])
263
+ end
264
+
265
+ assert_raise Offroad::CargoStreamerError, "Expect exception for cargo name that's bad in HTML comments" do
266
+ cs.write_cargo_section("whatever--foobar", [test_rec("test")])
267
+ end
268
+
269
+ assert_raise Offroad::CargoStreamerError, "Expect exception for cargo name that's multiline" do
270
+ cs.write_cargo_section("whatever\nfoobar", [test_rec("test")])
271
+ end
272
+ end
273
+
274
+ agnostic_test "cannot encode invalid records except with :skip_validation option" do
275
+ rec = TestModel.new() # Nothing set to the required "data" field
276
+ assert_equal false, rec.valid?
277
+ assert_raise Offroad::CargoStreamerError do
278
+ generate_cargo_string "foo" => [[rec]]
279
+ end
280
+ assert_nothing_raised Offroad::CargoStreamerError do
281
+ generate_cargo_string({"foo" => [[rec]]}, true)
282
+ end
283
+ rec.data = "Something"
284
+ assert_nothing_raised do
285
+ generate_cargo_string "foo" => [[rec]]
286
+ end
287
+ end
288
+
289
+ agnostic_test "cannot encode a non-safe model class" do
290
+ begin
291
+ TestModel.set_safe
292
+ assert_nothing_raised do
293
+ str = generate_cargo_string "test" => [[test_rec("ABC")]]
294
+ end
295
+ TestModel.set_unsafe
296
+ assert_raise Offroad::CargoStreamerError do
297
+ str = generate_cargo_string "test" => [[test_rec("ABC")]]
298
+ end
299
+ ensure
300
+ TestModel.set_safe
301
+ end
302
+ end
303
+
304
+ agnostic_test "cannot trick cargo streamer into decoding a non-safe model class" do
305
+ begin
306
+ TestModel.set_safe
307
+ str = generate_cargo_string "test" => [[test_rec("Stuff")]]
308
+
309
+ assert_nothing_raised do
310
+ retrieve_cargo_from_string(str)
311
+ end
312
+ TestModel.set_unsafe
313
+ assert_raise Offroad::CargoStreamerError do
314
+ retrieve_cargo_from_string(str)
315
+ end
316
+ ensure
317
+ TestModel.set_safe
318
+ end
319
+ end
320
+
321
+ agnostic_test "cargo streamer can read directly from a passed in string" do
322
+ str = generate_cargo_string("test" => [[test_rec("abc")]])
323
+ cs = Offroad::CargoStreamer.new(str, "r")
324
+ assert cs.has_cargo_named?("test")
325
+ end
326
+
327
+ agnostic_test "cannot have cargo streamer write to a passed in string" do
328
+ assert_raise Offroad::CargoStreamerError do
329
+ Offroad::CargoStreamer.new("", "w")
330
+ end
331
+ end
332
+ end