thingfish 0.5.0.pre20161103181816 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  # -*- ruby -*-
2
- #encoding: utf-8
2
+ # frozen_string_literal: true
3
3
 
4
4
  require 'pluggability'
5
5
  require 'strelka'
@@ -1,5 +1,5 @@
1
1
  # -*- ruby -*-
2
- #encoding: utf-8
2
+ # frozen_string_literal: true
3
3
 
4
4
  require 'thingfish' unless defined?( Thingfish )
5
5
  require 'thingfish/metastore' unless defined?( Thingfish::Metastore )
@@ -1,6 +1,6 @@
1
1
  # -*- ruby -*-
2
2
  # vim: set nosta noet ts=4 sw=4:
3
- # encoding: utf-8
3
+ # frozen_string_literal: true
4
4
 
5
5
  require 'securerandom'
6
6
 
@@ -1,5 +1,5 @@
1
1
  # -*- ruby -*-
2
- #encoding: utf-8
2
+ # frozen_string_literal: true
3
3
 
4
4
  require 'pluggability'
5
5
  require 'strelka/httprequest/acceptparams'
@@ -0,0 +1,51 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'digest/sha2'
5
+
6
+ require 'thingfish' unless defined?( Thingfish )
7
+ require 'thingfish/processor' unless defined?( Thingfish::Processor )
8
+
9
+
10
+ # Calculate and store a sha256 checksum for a resource.
11
+ class Thingfish::Processor::SHA256 < Thingfish::Processor
12
+ extend Loggability
13
+
14
+ # The chunk size to read
15
+ CHUNK_SIZE = 32 * 1024
16
+
17
+ # Loggability API -- log to the :thingfish logger
18
+ log_to :thingfish
19
+
20
+ # The list of handled types
21
+ handled_types '*/*'
22
+
23
+
24
+ ### Synchronous processor API -- generate a checksum during upload.
25
+ def on_request( request )
26
+ request.add_metadata( :checksum => self.checksum(request.body) )
27
+ request.related_resources.each_pair do |io, metadata|
28
+ metadata[ :checksum ] = self.checksum( io )
29
+ end
30
+ end
31
+
32
+
33
+ #########
34
+ protected
35
+ #########
36
+
37
+ ### Given an +io+, return a sha256 checksum of it's contents.
38
+ def checksum( io )
39
+ digest = Digest::SHA256.new
40
+ buf = String.new
41
+
42
+ while io.read( CHUNK_SIZE, buf )
43
+ digest.update( buf )
44
+ end
45
+
46
+ io.rewind
47
+ return digest.hexdigest
48
+ end
49
+
50
+ end # class Thingfish::Processor::SHA256
51
+
@@ -1,5 +1,5 @@
1
1
  # -*- ruby -*-
2
- #encoding: utf-8
2
+ # frozen_string_literal: true
3
3
  # vim: set nosta noet ts=4 sw=4 ft=ruby:
4
4
 
5
5
  require 'time'
@@ -42,25 +42,32 @@ Loggability.format_with( :color ) if $stdout.tty?
42
42
 
43
43
 
44
44
  ### Mock with RSpec
45
- RSpec.configure do |c|
45
+ RSpec.configure do |config|
46
46
  include Strelka::Constants
47
47
  include Thingfish::SpecHelpers
48
48
  include Thingfish::SpecHelpers::Constants
49
49
 
50
- c.run_all_when_everything_filtered = true
51
- c.filter_run :focus
52
- c.order = 'random'
53
- c.mock_with( :rspec ) do |mock|
50
+ config.mock_with( :rspec ) do |mock|
54
51
  mock.syntax = :expect
55
52
  end
56
53
 
57
- c.include( Loggability::SpecHelpers )
58
- c.include( Mongrel2::SpecHelpers )
59
- c.include( Mongrel2::Constants )
60
- c.include( Mongrel2::Config::DSL )
61
- c.include( Strelka::Constants )
62
- c.include( Strelka::Testing )
63
- c.include( Thingfish::SpecHelpers )
54
+ config.disable_monkey_patching!
55
+ config.example_status_persistence_file_path = "spec/.status"
56
+ config.filter_run :focus
57
+ config.filter_run_when_matching :focus
58
+ config.order = :random
59
+ config.profile_examples = 5
60
+ config.run_all_when_everything_filtered = true
61
+ config.shared_context_metadata_behavior = :apply_to_host_groups
62
+ # config.warnings = true
63
+
64
+ config.include( Loggability::SpecHelpers )
65
+ config.include( Mongrel2::SpecHelpers )
66
+ config.include( Mongrel2::Constants )
67
+ config.include( Mongrel2::Config::DSL )
68
+ config.include( Strelka::Constants )
69
+ config.include( Strelka::Testing )
70
+ config.include( Thingfish::SpecHelpers )
64
71
  end
65
72
 
66
73
  # vim: set nosta noet ts=4 sw=4:
@@ -7,7 +7,7 @@ require 'thingfish/datastore'
7
7
  require 'thingfish/behaviors'
8
8
 
9
9
 
10
- describe Thingfish::Datastore, "memory" do
10
+ RSpec.describe Thingfish::Datastore, "memory" do
11
11
 
12
12
  let( :store ) { Thingfish::Datastore.create(:memory) }
13
13
 
@@ -9,7 +9,7 @@ class TestingDatastore < Thingfish::Datastore
9
9
  end
10
10
 
11
11
 
12
- describe Thingfish::Datastore do
12
+ RSpec.describe Thingfish::Datastore do
13
13
 
14
14
  it "is abstract" do
15
15
  expect { described_class.new }.to raise_error( NoMethodError, /private/i )
@@ -7,27 +7,18 @@ require 'thingfish/handler'
7
7
  require 'thingfish/processor'
8
8
 
9
9
 
10
- describe Thingfish::Handler do
10
+ RSpec.describe Thingfish::Handler do
11
11
 
12
- EVENT_SOCKET_URI = 'tcp://127.0.0.1:0'
12
+ EVENT_SOCKET_URI = 'tcp://127.0.0.1:*'
13
13
 
14
14
  before( :all ) do
15
15
  Thingfish::Handler.configure( :event_socket_uri => EVENT_SOCKET_URI )
16
16
  Thingfish::Handler.install_plugins
17
17
  end
18
18
 
19
- before( :each ) do
20
- @png_io = StringIO.new( TEST_PNG_DATA.dup )
21
- @text_io = StringIO.new( TEST_TEXT_DATA.dup )
22
- @handler = described_class.new( TEST_APPID, TEST_SEND_SPEC, TEST_RECV_SPEC )
23
- end
24
-
25
-
26
- after( :each ) do
27
- @handler.shutdown
28
- end
29
-
30
- # let( :handler ) { described_class.new(TEST_APPID, TEST_SEND_SPEC, TEST_RECV_SPEC) }
19
+ let( :png_io ) { StringIO.new( TEST_PNG_DATA.dup ) }
20
+ let( :text_io ) { StringIO.new( TEST_TEXT_DATA.dup ) }
21
+ let( :handler ) { described_class.new(TEST_APPID, TEST_SEND_SPEC, TEST_RECV_SPEC) }
31
22
 
32
23
 
33
24
  #
@@ -51,7 +42,7 @@ describe Thingfish::Handler do
51
42
 
52
43
  it 'returns interesting configuration info' do
53
44
  req = factory.get( '/serverinfo', content_type: 'text/plain' )
54
- res = @handler.handle( req )
45
+ res = handler.handle( req )
55
46
 
56
47
  expect( res.status_line ).to match( /200 ok/i )
57
48
  expect( res.headers ).to include( 'x-thingfish' )
@@ -73,7 +64,7 @@ describe Thingfish::Handler do
73
64
  req = factory.post( '/', TEST_TEXT_DATA )
74
65
  req.content_type = 'text/plain'
75
66
  req.headers.content_length = TEST_TEXT_DATA.bytesize
76
- res = @handler.handle( req )
67
+ res = handler.handle( req )
77
68
 
78
69
  expect( res.status_line ).to match( /201 created/i )
79
70
  expect( res.headers.location.to_s ).to match( %r:/#{UUID_PATTERN}$: )
@@ -100,18 +91,18 @@ describe Thingfish::Handler do
100
91
  with( spool_path, 'r', encoding: Encoding::ASCII_8BIT ).
101
92
  and_return( fh )
102
93
 
103
- start_req = factory.post( '/', nil,
94
+ start_req = factory.post( '/', '',
104
95
  x_mongrel2_upload_start: spool_path,
105
96
  content_length: upload_size,
106
97
  content_type: 'text/plain' )
107
- upload_req = factory.post( '/', nil,
98
+ upload_req = factory.post( '/', '',
108
99
  x_mongrel2_upload_start: spool_path,
109
100
  x_mongrel2_upload_done: spool_path,
110
101
  content_length: upload_size,
111
102
  content_type: 'text/plain' )
112
103
 
113
- start_res = @handler.dispatch_request( start_req )
114
- upload_res = @handler.dispatch_request( upload_req )
104
+ start_res = handler.dispatch_request( start_req )
105
+ upload_res = handler.dispatch_request( upload_req )
115
106
 
116
107
  expect( start_res ).to be_nil
117
108
 
@@ -151,7 +142,7 @@ describe Thingfish::Handler do
151
142
  req = factory.post( '/', TEST_TEXT_DATA )
152
143
  req.content_type = 'text/plain'
153
144
 
154
- res = @handler.handle( req )
145
+ res = handler.handle( req )
155
146
  res.body.rewind
156
147
  expect( res.status ).to be( HTTP::BAD_REQUEST )
157
148
  expect( res.body.read ).to match( /missing operational attribute/i )
@@ -166,60 +157,60 @@ describe Thingfish::Handler do
166
157
  }
167
158
  req = factory.post( '/', TEST_TEXT_DATA, headers )
168
159
  req.headers.content_length = TEST_TEXT_DATA.bytesize
169
- res = @handler.handle( req )
160
+ res = handler.handle( req )
170
161
 
171
162
  expect( res.status_line ).to match( /201 created/i )
172
163
  expect( res.headers.location.to_s ).to match( %r:/#{UUID_PATTERN}$: )
173
164
 
174
165
  uuid = res.headers.x_thingfish_uuid
175
- expect( @handler.metastore.fetch_value(uuid, 'title') ).
166
+ expect( handler.metastore.fetch_value(uuid, 'title') ).
176
167
  to eq( 'Muffin the Panda Goes To School' )
177
- expect( @handler.metastore.fetch_value(uuid, 'tags') ).to eq( 'rapper,ukraine,potap' )
168
+ expect( handler.metastore.fetch_value(uuid, 'tags') ).to eq( 'rapper,ukraine,potap' )
178
169
  end
179
170
 
180
171
 
181
172
  it 'replaces content via PUT' do
182
- uuid = @handler.datastore.save( @text_io )
183
- @handler.metastore.save( uuid, {'format' => 'text/plain'} )
173
+ uuid = handler.datastore.save( text_io )
174
+ handler.metastore.save( uuid, {'format' => 'text/plain'} )
184
175
 
185
- req = factory.put( "/#{uuid}", @png_io, content_type: 'image/png' )
186
- req.headers.content_length = @png_io.read.bytesize
187
- res = @handler.handle( req )
176
+ req = factory.put( "/#{uuid}", png_io, content_type: 'image/png' )
177
+ req.headers.content_length = png_io.read.bytesize
178
+ res = handler.handle( req )
188
179
 
189
180
  expect( res.status ).to eq( HTTP::NO_CONTENT )
190
- expect( @handler.datastore.fetch(uuid).read ).to eq( TEST_PNG_DATA )
191
- expect( @handler.metastore.fetch(uuid) ).to include( 'format' => 'image/png' )
181
+ expect( handler.datastore.fetch(uuid).read ).to eq( TEST_PNG_DATA )
182
+ expect( handler.metastore.fetch(uuid) ).to include( 'format' => 'image/png' )
192
183
  end
193
184
 
194
185
 
195
186
  it "doesn't care about the case of the UUID when replacing content via PUT" do
196
- uuid = @handler.datastore.save( @text_io )
197
- @handler.metastore.save( uuid, {'format' => 'text/plain'} )
187
+ uuid = handler.datastore.save( text_io )
188
+ handler.metastore.save( uuid, {'format' => 'text/plain'} )
198
189
 
199
- req = factory.put( "/#{uuid.upcase}", @png_io, content_type: 'image/png' )
200
- req.headers.content_length = @png_io.read.bytesize
201
- res = @handler.handle( req )
190
+ req = factory.put( "/#{uuid.upcase}", png_io, content_type: 'image/png' )
191
+ req.headers.content_length = png_io.read.bytesize
192
+ res = handler.handle( req )
202
193
 
203
194
  expect( res.status ).to eq( HTTP::NO_CONTENT )
204
- expect( @handler.datastore.fetch(uuid).read ).to eq( TEST_PNG_DATA )
205
- expect( @handler.metastore.fetch(uuid) ).to include( 'format' => 'image/png' )
195
+ expect( handler.datastore.fetch(uuid).read ).to eq( TEST_PNG_DATA )
196
+ expect( handler.metastore.fetch(uuid) ).to include( 'format' => 'image/png' )
206
197
  end
207
198
 
208
199
 
209
200
  it 'can fetch all uploaded data' do
210
- text_uuid = @handler.datastore.save( @text_io )
211
- @handler.metastore.save( text_uuid, {
201
+ text_uuid = handler.datastore.save( text_io )
202
+ handler.metastore.save( text_uuid, {
212
203
  'format' => 'text/plain',
213
- 'extent' => @text_io.string.bytesize
204
+ 'extent' => text_io.string.bytesize
214
205
  })
215
- png_uuid = @handler.datastore.save( @png_io )
216
- @handler.metastore.save( png_uuid, {
206
+ png_uuid = handler.datastore.save( png_io )
207
+ handler.metastore.save( png_uuid, {
217
208
  'format' => 'image/png',
218
- 'extent' => @png_io.string.bytesize
209
+ 'extent' => png_io.string.bytesize
219
210
  })
220
211
 
221
212
  req = factory.get( '/' )
222
- res = @handler.handle( req )
213
+ res = handler.handle( req )
223
214
  content = Yajl::Parser.parse( res.body.read )
224
215
 
225
216
  expect( res.status_line ).to match( /200 ok/i )
@@ -228,30 +219,30 @@ describe Thingfish::Handler do
228
219
  expect( content[0] ).to be_a( Hash )
229
220
  expect( content[0]['uri'] ).to eq( "#{req.base_uri}#{text_uuid}" )
230
221
  expect( content[0]['format'] ).to eq( "text/plain" )
231
- expect( content[0]['extent'] ).to eq( @text_io.string.bytesize )
222
+ expect( content[0]['extent'] ).to eq( text_io.string.bytesize )
232
223
  expect( content[1] ).to be_a( Hash )
233
224
  expect( content[1]['uri'] ).to eq( "#{req.base_uri}#{png_uuid}" )
234
225
  expect( content[1]['format'] ).to eq( 'image/png' )
235
- expect( content[1]['extent'] ).to eq( @png_io.string.bytesize )
226
+ expect( content[1]['extent'] ).to eq( png_io.string.bytesize )
236
227
  end
237
228
 
238
229
 
239
230
  it 'can fetch all related data for a single resource' do
240
- main_uuid = @handler.datastore.save( @png_io )
241
- @handler.metastore.save( main_uuid, {
231
+ main_uuid = handler.datastore.save( png_io )
232
+ handler.metastore.save( main_uuid, {
242
233
  'format' => 'image/png',
243
- 'extent' => @png_io.string.bytesize
234
+ 'extent' => png_io.string.bytesize
244
235
  })
245
- related_uuid = @handler.datastore.save( @png_io )
246
- @handler.metastore.save( related_uuid, {
236
+ related_uuid = handler.datastore.save( png_io )
237
+ handler.metastore.save( related_uuid, {
247
238
  'format' => 'image/png',
248
- 'extent' => @png_io.string.bytesize,
239
+ 'extent' => png_io.string.bytesize,
249
240
  'relation' => main_uuid,
250
241
  'relationship' => "twinsies"
251
242
  })
252
243
 
253
244
  req = factory.get( "/#{main_uuid}/related" )
254
- res = @handler.handle( req )
245
+ res = handler.handle( req )
255
246
  content = Yajl::Parser.parse( res.body.read )
256
247
 
257
248
  expect( res.status_line ).to match( /200 ok/i )
@@ -260,146 +251,190 @@ describe Thingfish::Handler do
260
251
  expect( content[0] ).to be_a( Hash )
261
252
  expect( content[0]['uri'] ).to eq( "#{req.base_uri}#{related_uuid}" )
262
253
  expect( content[0]['format'] ).to eq( "image/png" )
263
- expect( content[0]['extent'] ).to eq( @png_io.string.bytesize )
254
+ expect( content[0]['extent'] ).to eq( png_io.string.bytesize )
264
255
  expect( content[0]['uuid'] ).to eq( related_uuid )
265
256
  expect( content[0]['relation'] ).to eq( main_uuid )
266
257
  end
267
258
 
268
259
 
269
260
  it 'can fetch a related resource by name' do
270
- main_uuid = @handler.datastore.save( @png_io )
271
- @handler.metastore.save( main_uuid, {
261
+ main_uuid = handler.datastore.save( png_io )
262
+ handler.metastore.save( main_uuid, {
272
263
  'format' => 'image/png',
273
- 'extent' => @png_io.string.bytesize
264
+ 'extent' => png_io.string.bytesize
274
265
  })
275
- related_uuid = @handler.datastore.save( @png_io )
276
- @handler.metastore.save( related_uuid, {
266
+ related_uuid = handler.datastore.save( png_io )
267
+ handler.metastore.save( related_uuid, {
277
268
  'format' => 'image/png',
278
- 'extent' => @png_io.string.bytesize,
269
+ 'extent' => png_io.string.bytesize,
279
270
  'relation' => main_uuid,
280
- 'relationship' => "twinsies"
271
+ 'relationship' => "twinsies",
272
+ 'title' => 'Make America Smart Again.png',
273
+ 'checksum' => '123456'
281
274
  })
282
275
 
283
276
  req = factory.get( "/#{main_uuid}/related/twinsies" )
284
- res = @handler.handle( req )
277
+ res = handler.handle( req )
285
278
 
286
279
  expect( res.status_line ).to match( /200 ok/i )
287
280
  expect( res.headers.content_type ).to eq( 'image/png' )
281
+ expect( res.headers.etag ).to eq( '123456' )
282
+ expect( res.headers.content_disposition ).to eq( 'filename="Make America Smart Again.png"' )
288
283
  expect( res.body.read ).to eq( TEST_PNG_DATA )
289
284
  end
290
285
 
291
286
 
292
287
  it "404s when attempting to fetch a resource related to a non-existant resource" do
293
288
  req = factory.get( "/#{TEST_UUID}/related/twinsies" )
294
- res = @handler.handle( req )
289
+ res = handler.handle( req )
295
290
 
296
291
  expect( res.status_line ).to match( /404 not found/i )
297
292
  end
298
293
 
299
294
 
300
295
  it "404s when attempting to fetch a related resource that doesn't exist" do
301
- uuid = @handler.datastore.save( @png_io )
302
- @handler.metastore.save( uuid, {
296
+ uuid = handler.datastore.save( png_io )
297
+ handler.metastore.save( uuid, {
303
298
  'format' => 'image/png',
304
- 'extent' => @png_io.string.bytesize
299
+ 'extent' => png_io.string.bytesize
305
300
  })
306
301
 
307
302
  req = factory.get( "/#{uuid}/related/twinsies" )
308
- res = @handler.handle( req )
303
+ res = handler.handle( req )
309
304
 
310
305
  expect( res.status_line ).to match( /404 not found/i )
311
306
  end
312
307
 
313
308
 
314
309
  it "can fetch an uploaded chunk of data" do
315
- uuid = @handler.datastore.save( @png_io )
316
- @handler.metastore.save( uuid, {'format' => 'image/png'} )
310
+ uuid = handler.datastore.save( png_io )
311
+ handler.metastore.save( uuid, {'format' => 'image/png'} )
317
312
 
318
313
  req = factory.get( "/#{uuid}" )
319
- result = @handler.handle( req )
314
+ result = handler.handle( req )
320
315
 
321
316
  expect( result.status_line ).to match( /200 ok/i )
322
- expect( result.body.read ).to eq( @png_io.string )
317
+ expect( result.body.read ).to eq( png_io.string )
323
318
  expect( result.headers.content_type ).to eq( 'image/png' )
324
319
  end
325
320
 
326
321
 
327
322
  it "returns a 404 Not Found when asked to fetch an object that doesn't exist" do
328
323
  req = factory.get( "/#{TEST_UUID}" )
329
- result = @handler.handle( req )
324
+ result = handler.handle( req )
330
325
 
331
326
  expect( result.status_line ).to match( /404 not found/i )
332
327
  end
333
328
 
334
329
 
335
330
  it "returns a 404 Not Found when asked to fetch an object that doesn't exist in the metastore" do
336
- uuid = @handler.datastore.save( @png_io )
331
+ uuid = handler.datastore.save( png_io )
337
332
 
338
333
  req = factory.get( "/#{uuid}" )
339
- result = @handler.handle( req )
334
+ result = handler.handle( req )
340
335
 
341
336
  expect( result.status_line ).to match( /404 not found/i )
342
337
  end
343
338
 
344
339
 
345
340
  it "doesn't care about the case of the UUID when fetching uploaded data" do
346
- uuid = @handler.datastore.save( @png_io )
347
- @handler.metastore.save( uuid, {'format' => 'image/png'} )
341
+ uuid = handler.datastore.save( png_io )
342
+ handler.metastore.save( uuid, {'format' => 'image/png'} )
348
343
 
349
344
  req = factory.get( "/#{uuid.upcase}" )
350
- result = @handler.handle( req )
345
+ result = handler.handle( req )
351
346
 
352
347
  expect( result.status_line ).to match( /200 ok/i )
353
- expect( result.body.read ).to eq( @png_io.string )
348
+ expect( result.body.read ).to eq( png_io.string )
354
349
  expect( result.headers.content_type ).to eq( 'image/png' )
355
350
  end
356
351
 
357
352
 
358
- it "adds browser cache headers to resources with a checksum attribute" do
359
- uuid = @handler.datastore.save( @png_io )
360
- @handler.metastore.save( uuid, 'format' => 'image/png', 'checksum' => '123456' )
353
+ it "adds date cache headers to resources" do
354
+ created = Time.now
355
+ uuid = handler.datastore.save( png_io )
356
+ handler.metastore.save( uuid, 'format' => 'image/png', 'created' => created )
361
357
 
362
358
  req = factory.get( "/#{uuid}" )
363
- result = @handler.handle( req )
359
+ result = handler.handle( req )
360
+
361
+ expect( result.status_line ).to match( /200 ok/i )
362
+ expect( result.headers.last_modified ).to eq( created.httpdate )
363
+ end
364
+
365
+
366
+ it "adds content cache headers to resources with a checksum attribute" do
367
+ uuid = handler.datastore.save( png_io )
368
+ handler.metastore.save( uuid, 'format' => 'image/png', 'checksum' => '123456' )
369
+
370
+ req = factory.get( "/#{uuid}" )
371
+ result = handler.handle( req )
364
372
 
365
373
  expect( result.status_line ).to match( /200 ok/i )
366
374
  expect( result.headers.etag ).to eq( '123456' )
367
375
  end
368
376
 
369
377
 
370
- it "returns a 304 not modified for unchanged client cache requests" do
371
- uuid = @handler.datastore.save( @png_io )
372
- @handler.metastore.save( uuid, 'format' => 'image/png', 'checksum' => '123456' )
378
+ it "adds content disposition filename, if the resource has a title" do
379
+ uuid = handler.datastore.save( png_io )
380
+ handler.metastore.save( uuid, {'format' => 'image/png', 'title' => 'spょler"py.txt'} )
373
381
 
374
382
  req = factory.get( "/#{uuid}" )
375
- req.headers[ :if_none_match ] = '123456'
376
- result = @handler.handle( req )
383
+ result = handler.handle( req )
384
+
385
+ expect( result.status_line ).to match( /200 ok/i )
386
+ expect( result.body.read ).to eq( png_io.string )
387
+ expect( result.headers.content_type ).to eq( 'image/png' )
388
+ expect( result.headers.content_disposition ).to eq( 'filename="sp?ler\"py.txt"' )
389
+ end
390
+
391
+
392
+ it "returns a 304 not modified for unchanged date cache requests" do
393
+ created = Time.now
394
+ uuid = handler.datastore.save( png_io )
395
+ handler.metastore.save( uuid, 'format' => 'image/png', 'created' => created )
396
+
397
+ req = factory.get( "/#{uuid}" )
398
+ req.headers[ :if_modified_since ] = ( Time.now - 300 ).httpdate
399
+ result = handler.handle( req )
377
400
 
378
401
  expect( result.status_line ).to match( /304 not modified/i )
379
402
  expect( result.body.read ).to be_empty
380
403
  end
381
404
 
382
405
 
406
+ it "returns a 304 not modified for unchanged content cache requests" do
407
+ uuid = handler.datastore.save( png_io )
408
+ handler.metastore.save( uuid, 'format' => 'image/png', 'checksum' => '123456' )
409
+
410
+ req = factory.get( "/#{uuid}" )
411
+ req.headers[ :if_none_match ] = '123456'
412
+ result = handler.handle( req )
413
+
414
+ expect( result.status_line ).to match( /304 not modified/i )
415
+ expect( result.body.read ).to be_empty
416
+ end
417
+
383
418
 
384
419
  it "can remove everything associated with an object id" do
385
- uuid = @handler.datastore.save( @png_io )
386
- @handler.metastore.save( uuid, {
420
+ uuid = handler.datastore.save( png_io )
421
+ handler.metastore.save( uuid, {
387
422
  'format' => 'image/png',
388
423
  'extent' => 288,
389
424
  })
390
425
 
391
426
  req = factory.delete( "/#{uuid}" )
392
- result = @handler.handle( req )
427
+ result = handler.handle( req )
393
428
 
394
429
  expect( result.status_line ).to match( /200 ok/i )
395
- expect( @handler.metastore.include?(uuid) ).to be_falsey
396
- expect( @handler.datastore.include?(uuid) ).to be_falsey
430
+ expect( handler.metastore.include?(uuid) ).to be_falsey
431
+ expect( handler.datastore.include?(uuid) ).to be_falsey
397
432
  end
398
433
 
399
434
 
400
435
  it "returns a 404 Not Found when asked to remove an object that doesn't exist" do
401
436
  req = factory.delete( "/#{TEST_UUID}" )
402
- result = @handler.handle( req )
437
+ result = handler.handle( req )
403
438
 
404
439
  expect( result.status_line ).to match( /404 not found/i )
405
440
  end
@@ -417,15 +452,15 @@ describe Thingfish::Handler do
417
452
  end
418
453
 
419
454
  it "can fetch the metadata associated with uploaded data" do
420
- uuid = @handler.datastore.save( @png_io )
421
- @handler.metastore.save( uuid, {
455
+ uuid = handler.datastore.save( png_io )
456
+ handler.metastore.save( uuid, {
422
457
  'format' => 'image/png',
423
458
  'extent' => 288,
424
459
  'created' => Time.at(1378313840),
425
460
  })
426
461
 
427
462
  req = factory.get( "/#{uuid}/metadata" )
428
- result = @handler.handle( req )
463
+ result = handler.handle( req )
429
464
  content = result.body.read
430
465
 
431
466
  content_hash = Yajl::Parser.parse( content )
@@ -433,7 +468,7 @@ describe Thingfish::Handler do
433
468
  expect( result.status ).to eq( 200 )
434
469
  expect( result.headers.content_type ).to eq( 'application/json' )
435
470
  expect( content_hash ).to be_a( Hash )
436
- expect( content_hash['oid'] ).to eq( uuid )
471
+ expect( content_hash['uuid'] ).to eq( uuid )
437
472
  expect( content_hash['extent'] ).to eq( 288 )
438
473
  expect( content_hash['created'] ).to eq( Time.at(1378313840).to_s )
439
474
  end
@@ -441,21 +476,21 @@ describe Thingfish::Handler do
441
476
 
442
477
  it "returns a 404 Not Found when fetching metadata for an object that doesn't exist" do
443
478
  req = factory.get( "/#{TEST_UUID}/metadata" )
444
- result = @handler.handle( req )
479
+ result = handler.handle( req )
445
480
 
446
481
  expect( result.status_line ).to match( /404 not found/i )
447
482
  end
448
483
 
449
484
 
450
485
  it "can fetch a value for a single metadata key" do
451
- uuid = @handler.datastore.save( @png_io )
452
- @handler.metastore.save( uuid, {
486
+ uuid = handler.datastore.save( png_io )
487
+ handler.metastore.save( uuid, {
453
488
  'format' => 'image/png',
454
489
  'extent' => 288,
455
490
  })
456
491
 
457
492
  req = factory.get( "/#{uuid}/metadata/extent" )
458
- result = @handler.handle( req )
493
+ result = handler.handle( req )
459
494
  result.body.rewind
460
495
  content = result.body.read
461
496
 
@@ -467,21 +502,21 @@ describe Thingfish::Handler do
467
502
 
468
503
  it "returns a 404 Not Found when fetching a single metadata value for a uuid that doesn't exist" do
469
504
  req = factory.get( "/#{TEST_UUID}/metadata/extent" )
470
- result = @handler.handle( req )
505
+ result = handler.handle( req )
471
506
 
472
507
  expect( result.status_line ).to match( /404 not found/i )
473
508
  end
474
509
 
475
510
 
476
511
  it "doesn't error when fetching a non-existent metadata value" do
477
- uuid = @handler.datastore.save( @png_io )
478
- @handler.metastore.save( uuid, {
512
+ uuid = handler.datastore.save( png_io )
513
+ handler.metastore.save( uuid, {
479
514
  'format' => 'image/png',
480
515
  'extent' => 288,
481
516
  })
482
517
 
483
518
  req = factory.get( "/#{uuid}/metadata/hururrgghh" )
484
- result = @handler.handle( req )
519
+ result = handler.handle( req )
485
520
 
486
521
  content = Yajl::Parser.parse( result.body.read )
487
522
 
@@ -493,59 +528,58 @@ describe Thingfish::Handler do
493
528
 
494
529
 
495
530
  it "can merge in new metadata for an existing resource with a POST" do
496
- uuid = @handler.datastore.save( @png_io )
497
- @handler.metastore.save( uuid, {
531
+ uuid = handler.datastore.save( png_io )
532
+ handler.metastore.save( uuid, {
498
533
  'format' => 'image/png',
499
- 'extent' => 288,
534
+ 'extent' => 288
500
535
  })
501
536
 
502
- body_json = Yajl.dump({ 'comment' => 'Ignore me!' })
537
+ body_json = Yajl.dump({ 'comment' => 'Ignore me!', 'uuid' => 123 })
503
538
  req = factory.post( "/#{uuid}/metadata", body_json, 'Content-type' => 'application/json' )
504
- result = @handler.handle( req )
539
+ result = handler.handle( req )
505
540
 
506
- expect( result.status ).to eq( HTTP::NO_CONTENT )
507
- expect( @handler.metastore.fetch_value(uuid, 'comment') ).to eq( 'Ignore me!' )
541
+ expect( result.status ).to eq( HTTP::OK )
542
+ expect( handler.metastore.fetch_value(uuid, 'comment') ).to eq( 'Ignore me!' )
543
+ expect( handler.metastore.fetch_value(uuid, 'uuid') ).to be_nil
508
544
  end
509
545
 
510
546
 
511
- it "returns FORBIDDEN when attempting to merge metadata with operational keys" do
512
- uuid = @handler.datastore.save( @png_io )
513
- @handler.metastore.save( uuid, {
547
+ it "ignores attempts to alter operational metadata when merging" do
548
+ uuid = handler.datastore.save( png_io )
549
+ handler.metastore.save( uuid, {
514
550
  'format' => 'image/png',
515
551
  'extent' => 288,
516
552
  })
517
553
 
518
554
  body_json = Yajl.dump({ 'format' => 'text/plain', 'comment' => 'Ignore me!' })
519
555
  req = factory.post( "/#{uuid}/metadata", body_json, 'Content-type' => 'application/json' )
520
- result = @handler.handle( req )
556
+ result = handler.handle( req )
521
557
 
522
- expect( result.status ).to eq( HTTP::FORBIDDEN )
523
- expect( result.body.string ).to match( /unable to alter protected metadata/i )
524
- expect( result.body.string ).to match( /format/i )
525
- expect( @handler.metastore.fetch_value(uuid, 'comment') ).to be_nil
526
- expect( @handler.metastore.fetch_value(uuid, 'format') ).to eq( 'image/png' )
558
+ expect( result.status ).to eq( HTTP::OK )
559
+ expect( handler.metastore.fetch_value(uuid, 'comment') ).to eq( 'Ignore me!' )
560
+ expect( handler.metastore.fetch_value(uuid, 'format') ).to eq( 'image/png' )
527
561
  end
528
562
 
529
563
 
530
564
  it "can create single metadata values with a POST" do
531
- uuid = @handler.datastore.save( @png_io )
532
- @handler.metastore.save( uuid, {
565
+ uuid = handler.datastore.save( png_io )
566
+ handler.metastore.save( uuid, {
533
567
  'format' => 'image/png',
534
568
  'extent' => 288,
535
569
  })
536
570
 
537
571
  req = factory.post( "/#{uuid}/metadata/comment", "urrrg", 'Content-type' => 'text/plain' )
538
- result = @handler.handle( req )
572
+ result = handler.handle( req )
539
573
 
540
574
  expect( result.status ).to eq( HTTP::CREATED )
541
575
  expect( result.headers.location ).to match( %r|#{uuid}/metadata/comment$| )
542
- expect( @handler.metastore.fetch_value(uuid, 'comment') ).to eq( 'urrrg' )
576
+ expect( handler.metastore.fetch_value(uuid, 'comment') ).to eq( 'urrrg' )
543
577
  end
544
578
 
545
579
 
546
580
  it "returns NOT_FOUND when attempting to create metadata for a non-existent object" do
547
581
  req = factory.post( "/#{TEST_UUID}/metadata/comment", "urrrg", 'Content-type' => 'text/plain' )
548
- result = @handler.handle( req )
582
+ result = handler.handle( req )
549
583
 
550
584
  expect( result.status ).to eq( HTTP::NOT_FOUND )
551
585
  expect( result.body.string ).to match( /no such object/i )
@@ -553,73 +587,73 @@ describe Thingfish::Handler do
553
587
 
554
588
 
555
589
  it "returns CONFLICT when attempting to create a single metadata value if it already exists" do
556
- uuid = @handler.datastore.save( @png_io )
557
- @handler.metastore.save( uuid, {
590
+ uuid = handler.datastore.save( png_io )
591
+ handler.metastore.save( uuid, {
558
592
  'format' => 'image/png',
559
593
  'extent' => 288,
560
594
  'comment' => 'nill bill'
561
595
  })
562
596
 
563
597
  req = factory.post( "/#{uuid}/metadata/comment", "urrrg", 'Content-type' => 'text/plain' )
564
- result = @handler.handle( req )
598
+ result = handler.handle( req )
565
599
 
566
600
  expect( result.status ).to eq( HTTP::CONFLICT )
567
601
  expect( result.body.string ).to match( /already exists/i )
568
- expect( @handler.metastore.fetch_value(uuid, 'comment') ).to eq( 'nill bill' )
602
+ expect( handler.metastore.fetch_value(uuid, 'comment') ).to eq( 'nill bill' )
569
603
  end
570
604
 
571
605
 
572
606
  it "can create single metadata values with a PUT" do
573
- uuid = @handler.datastore.save( @png_io )
574
- @handler.metastore.save( uuid, {
607
+ uuid = handler.datastore.save( png_io )
608
+ handler.metastore.save( uuid, {
575
609
  'format' => 'image/png',
576
610
  'extent' => 288,
577
611
  })
578
612
 
579
613
  req = factory.put( "/#{uuid}/metadata/comment", "urrrg", 'Content-type' => 'text/plain' )
580
- result = @handler.handle( req )
614
+ result = handler.handle( req )
581
615
 
582
616
  expect( result.status ).to eq( HTTP::NO_CONTENT )
583
- expect( @handler.metastore.fetch_value(uuid, 'comment') ).to eq( 'urrrg' )
617
+ expect( handler.metastore.fetch_value(uuid, 'comment') ).to eq( 'urrrg' )
584
618
  end
585
619
 
586
620
 
587
621
  it "can replace a single metadata value with a PUT" do
588
- uuid = @handler.datastore.save( @png_io )
589
- @handler.metastore.save( uuid, {
622
+ uuid = handler.datastore.save( png_io )
623
+ handler.metastore.save( uuid, {
590
624
  'format' => 'image/png',
591
625
  'extent' => 288,
592
626
  'comment' => 'nill bill'
593
627
  })
594
628
 
595
629
  req = factory.put( "/#{uuid}/metadata/comment", "urrrg", 'Content-type' => 'text/plain' )
596
- result = @handler.handle( req )
630
+ result = handler.handle( req )
597
631
 
598
632
  expect( result.status ).to eq( HTTP::NO_CONTENT )
599
- expect( @handler.metastore.fetch_value(uuid, 'comment') ).to eq( 'urrrg' )
633
+ expect( handler.metastore.fetch_value(uuid, 'comment') ).to eq( 'urrrg' )
600
634
  end
601
635
 
602
636
 
603
637
  it "returns FORBIDDEN when attempting to replace a operational metadata value with a PUT" do
604
- uuid = @handler.datastore.save( @png_io )
605
- @handler.metastore.save( uuid, {
638
+ uuid = handler.datastore.save( png_io )
639
+ handler.metastore.save( uuid, {
606
640
  'format' => 'image/png',
607
641
  'extent' => 288,
608
642
  'comment' => 'nill bill'
609
643
  })
610
644
 
611
645
  req = factory.put( "/#{uuid}/metadata/format", "image/gif", 'Content-type' => 'text/plain' )
612
- result = @handler.handle( req )
646
+ result = handler.handle( req )
613
647
 
614
648
  expect( result.status ).to eq( HTTP::FORBIDDEN )
615
649
  expect( result.body.string ).to match( /protected metadata/i )
616
- expect( @handler.metastore.fetch_value(uuid, 'format') ).to eq( 'image/png' )
650
+ expect( handler.metastore.fetch_value(uuid, 'format') ).to eq( 'image/png' )
617
651
  end
618
652
 
619
653
 
620
654
  it "can replace all metadata with a PUT" do
621
- uuid = @handler.datastore.save( @png_io )
622
- @handler.metastore.save( uuid, {
655
+ uuid = handler.datastore.save( png_io )
656
+ handler.metastore.save( uuid, {
623
657
  'format' => 'image/png',
624
658
  'extent' => 288,
625
659
  'comment' => 'nill bill',
@@ -628,19 +662,19 @@ describe Thingfish::Handler do
628
662
 
629
663
  req = factory.put( "/#{uuid}/metadata", %[{"comment":"Yeah."}],
630
664
  'Content-type' => 'application/json' )
631
- result = @handler.handle( req )
665
+ result = handler.handle( req )
632
666
 
633
- expect( result.status ).to eq( HTTP::NO_CONTENT )
634
- expect( @handler.metastore.fetch_value(uuid, 'comment') ).to eq( 'Yeah.' )
635
- expect( @handler.metastore.fetch_value(uuid, 'format') ).to eq( 'image/png' )
636
- expect( @handler.metastore ).to_not include( 'ephemeral' )
667
+ expect( result.status ).to eq( HTTP::OK )
668
+ expect( handler.metastore.fetch_value(uuid, 'comment') ).to eq( 'Yeah.' )
669
+ expect( handler.metastore.fetch_value(uuid, 'format') ).to eq( 'image/png' )
670
+ expect( handler.metastore ).to_not include( 'ephemeral' )
637
671
  end
638
672
 
639
673
 
640
674
  it "can remove all non-default metadata with a DELETE" do
641
675
  timestamp = Time.now.getgm
642
- uuid = @handler.datastore.save( @png_io )
643
- @handler.metastore.save( uuid, {
676
+ uuid = handler.datastore.save( png_io )
677
+ handler.metastore.save( uuid, {
644
678
  'format' => 'image/png',
645
679
  'extent' => 288,
646
680
  'comment' => 'nill bill',
@@ -650,49 +684,49 @@ describe Thingfish::Handler do
650
684
  })
651
685
 
652
686
  req = factory.delete( "/#{uuid}/metadata" )
653
- result = @handler.handle( req )
687
+ result = handler.handle( req )
654
688
 
655
- expect( result.status ).to eq( HTTP::NO_CONTENT )
656
- expect( result.body.string ).to be_empty
657
- expect( @handler.metastore.fetch_value(uuid, 'format') ).to eq( 'image/png' )
658
- expect( @handler.metastore.fetch_value(uuid, 'extent') ).to eq( 288 )
659
- expect( @handler.metastore.fetch_value(uuid, 'uploadaddress') ).to eq( '127.0.0.1' )
660
- expect( @handler.metastore.fetch_value(uuid, 'created') ).to eq( timestamp )
689
+ expect( result.status ).to eq( HTTP::OK )
690
+ expect( result.body.string ).to_not be_empty
691
+ expect( handler.metastore.fetch_value(uuid, 'format') ).to eq( 'image/png' )
692
+ expect( handler.metastore.fetch_value(uuid, 'extent') ).to eq( 288 )
693
+ expect( handler.metastore.fetch_value(uuid, 'uploadaddress') ).to eq( '127.0.0.1' )
694
+ expect( handler.metastore.fetch_value(uuid, 'created') ).to eq( timestamp )
661
695
 
662
- expect( @handler.metastore.fetch_value(uuid, 'comment') ).to be_nil
663
- expect( @handler.metastore.fetch_value(uuid, 'useragent') ).to be_nil
696
+ expect( handler.metastore.fetch_value(uuid, 'comment') ).to be_nil
697
+ expect( handler.metastore.fetch_value(uuid, 'useragent') ).to be_nil
664
698
  end
665
699
 
666
700
 
667
701
  it "can remove a single metadata value with DELETE" do
668
- uuid = @handler.datastore.save( @png_io )
669
- @handler.metastore.save( uuid, {
702
+ uuid = handler.datastore.save( png_io )
703
+ handler.metastore.save( uuid, {
670
704
  'format' => 'image/png',
671
705
  'comment' => 'nill bill'
672
706
  })
673
707
 
674
708
  req = factory.delete( "/#{uuid}/metadata/comment" )
675
- result = @handler.handle( req )
709
+ result = handler.handle( req )
676
710
 
677
711
  expect( result.status ).to eq( HTTP::NO_CONTENT )
678
712
  expect( result.body.string ).to be_empty
679
- expect( @handler.metastore.fetch_value(uuid, 'comment') ).to be_nil
680
- expect( @handler.metastore.fetch_value(uuid, 'format') ).to eq( 'image/png' )
713
+ expect( handler.metastore.fetch_value(uuid, 'comment') ).to be_nil
714
+ expect( handler.metastore.fetch_value(uuid, 'format') ).to eq( 'image/png' )
681
715
  end
682
716
 
683
717
 
684
718
  it "returns FORBIDDEN when attempting to remove a operational metadata value with a DELETE" do
685
- uuid = @handler.datastore.save( @png_io )
686
- @handler.metastore.save( uuid, {
719
+ uuid = handler.datastore.save( png_io )
720
+ handler.metastore.save( uuid, {
687
721
  'format' => 'image/png'
688
722
  })
689
723
 
690
724
  req = factory.delete( "/#{uuid}/metadata/format" )
691
- result = @handler.handle( req )
725
+ result = handler.handle( req )
692
726
 
693
727
  expect( result.status ).to eq( HTTP::FORBIDDEN )
694
728
  expect( result.body.string ).to match( /protected metadata/i )
695
- expect( @handler.metastore.fetch_value(uuid, 'format') ).to eq( 'image/png' )
729
+ expect( handler.metastore.fetch_value(uuid, 'format') ).to eq( 'image/png' )
696
730
  end
697
731
  end
698
732
 
@@ -777,23 +811,23 @@ describe Thingfish::Handler do
777
811
 
778
812
  req = factory.post( '/', TEST_TEXT_DATA, content_type: 'text/plain' )
779
813
  req.headers.content_length = TEST_TEXT_DATA.bytesize
780
- res = @handler.handle( req )
814
+ res = handler.handle( req )
781
815
  uuid = res.headers.x_thingfish_uuid
782
816
 
783
- Thingfish.logger.debug "Metastore contains: %p" % [ @handler.metastore.storage ]
817
+ Thingfish.logger.debug "Metastore contains: %p" % [ handler.metastore.storage ]
784
818
 
785
- expect( @handler.metastore.fetch(uuid) ).
819
+ expect( handler.metastore.fetch(uuid) ).
786
820
  to include( 'test:comment' => 'Yo, it totally worked.')
787
- related_uuids = @handler.metastore.fetch_related_oids( uuid )
821
+ related_uuids = handler.metastore.fetch_related_oids( uuid )
788
822
  expect( related_uuids.size ).to eq( 1 )
789
823
 
790
824
  r_uuid = related_uuids.first.downcase
791
- expect( @handler.metastore.fetch_value(r_uuid, 'relation') ).to eq( uuid )
792
- expect( @handler.metastore.fetch_value(r_uuid, 'format') ).to eq( 'text/plain' )
793
- expect( @handler.metastore.fetch_value(r_uuid, 'extent') ).to eq( 9 )
794
- expect( @handler.metastore.fetch_value(r_uuid, 'relationship') ).to eq( 'comment' )
825
+ expect( handler.metastore.fetch_value(r_uuid, 'relation') ).to eq( uuid )
826
+ expect( handler.metastore.fetch_value(r_uuid, 'format') ).to eq( 'text/plain' )
827
+ expect( handler.metastore.fetch_value(r_uuid, 'extent') ).to eq( 9 )
828
+ expect( handler.metastore.fetch_value(r_uuid, 'relationship') ).to eq( 'comment' )
795
829
 
796
- expect( @handler.datastore.fetch(r_uuid).read ).to eq( 'Chunkers!' )
830
+ expect( handler.datastore.fetch(r_uuid).read ).to eq( 'Chunkers!' )
797
831
  end
798
832
 
799
833
 
@@ -802,7 +836,7 @@ describe Thingfish::Handler do
802
836
  processor = described_class.processors.first
803
837
 
804
838
  req = factory.post( "/#{TEST_UUID}/metadata", TEST_TEXT_DATA, content_type: 'text/plain' )
805
- @handler.handle( req )
839
+ handler.handle( req )
806
840
 
807
841
  expect( processor.was_called ).to be_falsey
808
842
  end
@@ -811,11 +845,11 @@ describe Thingfish::Handler do
811
845
  it "processes responses" do
812
846
  described_class.configure( :processors => %w[test] )
813
847
 
814
- uuid = @handler.datastore.save( @text_io )
815
- @handler.metastore.save( uuid, {'format' => 'text/plain'} )
848
+ uuid = handler.datastore.save( text_io )
849
+ handler.metastore.save( uuid, {'format' => 'text/plain'} )
816
850
 
817
851
  req = factory.get( "/#{uuid}" )
818
- res = @handler.handle( req )
852
+ res = handler.handle( req )
819
853
 
820
854
  res.body.rewind
821
855
  expect( res.body.read ).to eq( TEST_TEXT_DATA.reverse )
@@ -826,11 +860,11 @@ describe Thingfish::Handler do
826
860
  described_class.configure( :processors => %w[test] )
827
861
  processor = described_class.processors.first
828
862
 
829
- uuid = @handler.datastore.save( @text_io )
830
- @handler.metastore.save( uuid, {'format' => 'text/plain'} )
863
+ uuid = handler.datastore.save( text_io )
864
+ handler.metastore.save( uuid, {'format' => 'text/plain'} )
831
865
 
832
866
  req = factory.get( "/#{uuid}/metadata" )
833
- @handler.handle( req )
867
+ handler.handle( req )
834
868
 
835
869
  expect( processor.was_called ).to be_falsey
836
870
  end
@@ -846,12 +880,12 @@ describe Thingfish::Handler do
846
880
  end
847
881
 
848
882
  before( :each ) do
849
- @handler.setup_event_socket
883
+ handler.setup_event_socket
850
884
 
851
- @subsock = Mongrel2.zmq_context.socket( :SUB )
852
- @subsock.linger = 0
885
+ @subsock = CZTop::Socket::SUB.new
886
+ @subsock.options.linger = 0
853
887
  @subsock.subscribe( '' )
854
- @subsock.connect( @handler.event_socket.endpoint )
888
+ @subsock.connect( handler.event_socket.last_endpoint )
855
889
  end
856
890
 
857
891
  after( :each ) do
@@ -861,20 +895,20 @@ describe Thingfish::Handler do
861
895
  it "publishes notifications about uploaded assets to a PUBSUB socket" do
862
896
  req = factory.post( '/', TEST_TEXT_DATA, content_type: 'text/plain' )
863
897
  req.headers.content_length = TEST_TEXT_DATA.bytesize
864
- res = @handler.handle( req )
865
898
 
866
- handles = ZMQ.select( [@subsock], nil, nil, 0 )
867
- expect( handles ).to be_an( Array )
868
- expect( handles[0].size ).to eq( 1 )
869
- expect( handles[0].first ).to be( @subsock )
899
+ poller = CZTop::Poller.new
900
+ poller.add_reader( @subsock )
901
+
902
+ handler.handle( req )
903
+ event = poller.wait( 500 )
904
+
905
+ expect( event ).to_not be_nil
870
906
 
871
- event = @subsock.recv
872
- expect( @subsock.rcvmore? ).to be_truthy
873
- expect( event ).to eq( 'created' )
907
+ message = event.socket.receive
908
+ expect( message.frames.count ).to be( 2 )
874
909
 
875
- resource = @subsock.recv
876
- expect( @subsock.rcvmore? ).to be_falsey
877
- expect( resource ).to match( /^\{"uuid":"#{UUID_PATTERN}"\}$/ )
910
+ expect( message.frames.first.to_s ).to eq( 'created' )
911
+ expect( message.frames.last.to_s ).to match( /^\{"uuid":"#{UUID_PATTERN}"\}$/ )
878
912
  end
879
913
  end
880
914