thingfish 0.5.0.pre20161103181816 → 0.8.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.
@@ -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