mieps_http-2 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.
@@ -0,0 +1,794 @@
1
+ require 'helper'
2
+
3
+ RSpec.describe HTTP2::Stream do
4
+ before(:each) do
5
+ @client = Client.new
6
+ @stream = @client.new_stream
7
+ end
8
+
9
+ context 'stream states' do
10
+ it 'should initiliaze all streams to IDLE' do
11
+ expect(@stream.state).to eq :idle
12
+ end
13
+
14
+ it 'should set custom stream priority' do
15
+ stream = @client.new_stream(weight: 3, dependency: 2, exclusive: true)
16
+ expect(stream.weight).to eq 3
17
+ end
18
+
19
+ context 'idle' do
20
+ it 'should transition to open on sent HEADERS' do
21
+ @stream.send HEADERS.deep_dup
22
+ expect(@stream.state).to eq :open
23
+ end
24
+ it 'should transition to open on received HEADERS' do
25
+ @stream.receive HEADERS
26
+ expect(@stream.state).to eq :open
27
+ end
28
+ it 'should transition to reserved (local) on sent PUSH_PROMISE' do
29
+ @stream.send PUSH_PROMISE.deep_dup
30
+ expect(@stream.state).to eq :reserved_local
31
+ end
32
+ it 'should transition to reserved (remote) on received PUSH_PROMISE' do
33
+ @stream.receive PUSH_PROMISE
34
+ expect(@stream.state).to eq :reserved_remote
35
+ end
36
+ it 'should reprioritize stream on sent PRIORITY' do
37
+ expect { @stream.send PRIORITY.dup }.to_not raise_error
38
+ expect(@stream.weight).to eq 20
39
+ end
40
+ it 'should reprioritize stream on received PRIORITY' do
41
+ expect { @stream.send PRIORITY.dup }.to_not raise_error
42
+ expect(@stream.weight).to eq 20
43
+ end
44
+ end
45
+
46
+ context 'reserved (local)' do
47
+ before(:each) { @stream.send PUSH_PROMISE.deep_dup }
48
+
49
+ it 'should transition on sent PUSH_PROMISE' do
50
+ expect(@stream.state).to eq :reserved_local
51
+ end
52
+
53
+ it 'should allow HEADERS to be sent' do
54
+ expect { @stream.send HEADERS.deep_dup }.to_not raise_error
55
+ end
56
+
57
+ it 'should raise error if sending invalid frames' do
58
+ (FRAME_TYPES - [HEADERS, RST_STREAM]).each do |type|
59
+ expect { @stream.dup.send type }.to raise_error InternalError
60
+ end
61
+ end
62
+
63
+ it 'should raise error on receipt of invalid frames' do
64
+ what_types = (FRAME_TYPES - [PRIORITY, RST_STREAM, WINDOW_UPDATE])
65
+ what_types.each do |type|
66
+ expect { @stream.dup.receive type }.to raise_error InternalError
67
+ end
68
+ end
69
+
70
+ it 'should transition to half closed (remote) on sent HEADERS' do
71
+ @stream.send HEADERS.deep_dup
72
+ expect(@stream.state).to eq :half_closed_remote
73
+ end
74
+
75
+ it 'should transition to closed on sent RST_STREAM' do
76
+ @stream.close
77
+ expect(@stream.state).to eq :closed
78
+ end
79
+
80
+ it 'should transition to closed on received RST_STREAM' do
81
+ @stream.receive RST_STREAM
82
+ expect(@stream.state).to eq :closed
83
+ end
84
+
85
+ it 'should reprioritize stream on PRIORITY' do
86
+ expect { @stream.receive PRIORITY }.to_not raise_error
87
+ expect(@stream.weight).to eq 20
88
+ end
89
+
90
+ it 'should increment remote_window on received WINDOW_UPDATE' do
91
+ expect { @stream.receive WINDOW_UPDATE }.to_not raise_error
92
+ expect(@stream.remote_window).to eq DEFAULT_FLOW_WINDOW + WINDOW_UPDATE[:increment]
93
+ end
94
+ end
95
+
96
+ context 'reserved (remote)' do
97
+ before(:each) { @stream.receive PUSH_PROMISE }
98
+
99
+ it 'should transition on received PUSH_PROMISE' do
100
+ expect(@stream.state).to eq :reserved_remote
101
+ end
102
+
103
+ it 'should raise error if sending invalid frames' do
104
+ (FRAME_TYPES - [PRIORITY, RST_STREAM, WINDOW_UPDATE]).each do |type|
105
+ expect { @stream.dup.send type }.to raise_error InternalError
106
+ end
107
+ end
108
+
109
+ it 'should raise error on receipt of invalid frames' do
110
+ (FRAME_TYPES - [HEADERS, RST_STREAM]).each do |type|
111
+ expect { @stream.dup.receive type }.to raise_error InternalError
112
+ end
113
+ end
114
+
115
+ it 'should transition to half closed (local) on received HEADERS' do
116
+ @stream.receive HEADERS
117
+ expect(@stream.state).to eq :half_closed_local
118
+ end
119
+
120
+ it 'should transition to closed on sent RST_STREAM' do
121
+ @stream.close
122
+ expect(@stream.state).to eq :closed
123
+ end
124
+
125
+ it 'should transition to closed on received RST_STREAM' do
126
+ @stream.receive RST_STREAM
127
+ expect(@stream.state).to eq :closed
128
+ end
129
+
130
+ it 'should reprioritize stream on PRIORITY' do
131
+ expect { @stream.send PRIORITY.dup }.to_not raise_error
132
+ expect(@stream.weight).to eq 20
133
+ end
134
+
135
+ it 'should increment local_window on sent WINDOW_UPDATE' do
136
+ expect { @stream.send WINDOW_UPDATE.dup }.to_not raise_error
137
+ expect(@stream.local_window).to eq DEFAULT_FLOW_WINDOW + WINDOW_UPDATE[:increment]
138
+ end
139
+ end
140
+
141
+ context 'open' do
142
+ before(:each) { @stream.receive HEADERS }
143
+
144
+ it 'should allow any valid frames types to be sent' do
145
+ (FRAME_TYPES - [PING, GOAWAY, SETTINGS]).each do |type|
146
+ expect { @stream.dup.send type.deep_dup }.to_not raise_error
147
+ end
148
+ end
149
+
150
+ it 'should allow frames of any type to be received' do
151
+ FRAME_TYPES.each do |type|
152
+ expect { @stream.dup.receive type }.to_not raise_error
153
+ end
154
+ end
155
+
156
+ it 'should transition to half closed (local) if sending END_STREAM' do
157
+ [DATA, HEADERS].each do |frame|
158
+ s, f = @stream.dup, frame.deep_dup
159
+ f[:flags] = [:end_stream]
160
+
161
+ s.send f
162
+ expect(s.state).to eq :half_closed_local
163
+ end
164
+ end
165
+
166
+ it 'should transition to half closed (remote) if receiving END_STREAM' do
167
+ [DATA, HEADERS].each do |frame|
168
+ s, f = @stream.dup, frame.dup
169
+ f[:flags] = [:end_stream]
170
+
171
+ s.receive f
172
+ expect(s.state).to eq :half_closed_remote
173
+ end
174
+ end
175
+
176
+ it 'should transition to half closed if remote opened with END_STREAM' do
177
+ s = @client.new_stream
178
+ hclose = HEADERS.dup
179
+ hclose[:flags] = [:end_stream]
180
+
181
+ s.receive hclose
182
+ expect(s.state).to eq :half_closed_remote
183
+ end
184
+
185
+ it 'should transition to half closed if local opened with END_STREAM' do
186
+ s = @client.new_stream
187
+ hclose = HEADERS.deep_dup
188
+ hclose[:flags] = [:end_stream]
189
+
190
+ s.send hclose
191
+ expect(s.state).to eq :half_closed_local
192
+ end
193
+
194
+ it 'should transition to closed if sending RST_STREAM' do
195
+ @stream.close
196
+ expect(@stream.state).to eq :closed
197
+ end
198
+
199
+ it 'should transition to closed if receiving RST_STREAM' do
200
+ @stream.receive RST_STREAM
201
+ expect(@stream.state).to eq :closed
202
+ end
203
+
204
+ it 'should emit :active on open transition' do
205
+ openp, openr = false, false
206
+ sp = @client.new_stream
207
+ sr = @client.new_stream
208
+ sp.on(:active) { openp = true }
209
+ sr.on(:active) { openr = true }
210
+
211
+ sp.receive HEADERS
212
+ sr.send HEADERS.deep_dup
213
+
214
+ expect(openp).to be_truthy
215
+ expect(openr).to be_truthy
216
+ end
217
+
218
+ it 'should not emit :active on transition from open' do
219
+ order, stream = [], @client.new_stream
220
+
221
+ stream.on(:active) { order << :active }
222
+ stream.on(:half_close) { order << :half_close }
223
+ stream.on(:close) { order << :close }
224
+
225
+ req = HEADERS.deep_dup
226
+ req[:flags] = [:end_headers]
227
+
228
+ stream.send req
229
+ stream.send DATA.dup
230
+ expect(order).to eq [:active, :half_close]
231
+ end
232
+
233
+ it 'should emit :close on close transition' do
234
+ closep, closer = false, false
235
+ sp, sr = @stream.dup, @stream.dup
236
+
237
+ sp.on(:close) { closep = true }
238
+ sr.on(:close) { closer = true }
239
+
240
+ sp.receive RST_STREAM
241
+ sr.close
242
+
243
+ expect(closep).to be_truthy
244
+ expect(closer).to be_truthy
245
+ end
246
+
247
+ it 'should emit :close after frame is processed' do
248
+ order, stream = [], @client.new_stream
249
+
250
+ stream.on(:active) { order << :active }
251
+ stream.on(:data) { order << :data }
252
+ stream.on(:half_close) { order << :half_close }
253
+ stream.on(:close) { order << :close }
254
+
255
+ req = HEADERS.deep_dup
256
+ req[:flags] = [:end_stream, :end_headers]
257
+
258
+ stream.send req
259
+ stream.receive HEADERS
260
+ stream.receive DATA
261
+
262
+ expect(order).to eq [:active, :half_close, :data, :close]
263
+ end
264
+
265
+ it 'should emit :close with reason' do
266
+ reason = nil
267
+ @stream.on(:close) { |r| reason = r }
268
+ @stream.receive RST_STREAM
269
+ expect(reason).not_to be_nil
270
+ end
271
+
272
+ it 'should reprioritize stream on sent PRIORITY' do
273
+ expect { @stream.send PRIORITY.dup }.to_not raise_error
274
+ expect(@stream.weight).to eq 20
275
+ end
276
+ it 'should reprioritize stream on received PRIORITY' do
277
+ expect { @stream.receive PRIORITY }.to_not raise_error
278
+ expect(@stream.weight).to eq 20
279
+ end
280
+ end
281
+
282
+ context 'half closed (local)' do
283
+ before(:each) { @stream.send HEADERS_END_STREAM.deep_dup }
284
+
285
+ it 'should raise error on attempt to send invalid frames' do
286
+ (FRAME_TYPES - [PRIORITY, RST_STREAM, WINDOW_UPDATE]).each do |frame|
287
+ expect { @stream.dup.send frame }.to raise_error InternalError
288
+ end
289
+ end
290
+
291
+ it 'should transition to closed on receipt of END_STREAM flag' do
292
+ [DATA, HEADERS, CONTINUATION].each do |frame|
293
+ s, f = @stream.dup, frame.dup
294
+ f[:flags] = [:end_stream]
295
+
296
+ s.receive f
297
+ expect(s.state).to eq :closed
298
+ end
299
+ end
300
+
301
+ it 'should transition to closed on receipt of RST_STREAM frame' do
302
+ @stream.receive RST_STREAM
303
+ expect(@stream.state).to eq :closed
304
+ end
305
+
306
+ it 'should transition to closed if RST_STREAM frame is sent' do
307
+ @stream.send RST_STREAM.deep_dup
308
+ expect(@stream.state).to eq :closed
309
+ end
310
+
311
+ it 'should ignore received WINDOW_UPDATE frames' do
312
+ expect { @stream.receive WINDOW_UPDATE }.to_not raise_error
313
+ expect(@stream.state).to eq :half_closed_local
314
+ end
315
+
316
+ it 'should ignore received PRIORITY frames' do
317
+ expect { @stream.receive PRIORITY }.to_not raise_error
318
+ expect(@stream.state).to eq :half_closed_local
319
+ end
320
+
321
+ it 'should reprioritize stream on sent PRIORITY' do
322
+ expect { @stream.send PRIORITY.dup }.to_not raise_error
323
+ expect(@stream.weight).to eq 20
324
+ end
325
+
326
+ it 'should reprioritize stream (and decendants) on received PRIORITY' do
327
+ expect { @stream.receive PRIORITY }.to_not raise_error
328
+ expect(@stream.weight).to eq 20
329
+ end
330
+
331
+ it 'should increment local_window on sent WINDOW_UPDATE' do
332
+ expect { @stream.send WINDOW_UPDATE.dup }.to_not raise_error
333
+ expect(@stream.local_window).to eq DEFAULT_FLOW_WINDOW + WINDOW_UPDATE[:increment]
334
+ end
335
+
336
+ it 'should emit :half_close event on transition' do
337
+ order = []
338
+ stream = @client.new_stream
339
+ stream.on(:active) { order << :active }
340
+ stream.on(:half_close) { order << :half_close }
341
+
342
+ req = HEADERS.deep_dup
343
+ req[:flags] = [:end_stream, :end_headers]
344
+
345
+ stream.send req
346
+ expect(order).to eq [:active, :half_close]
347
+ end
348
+
349
+ it 'should emit :close event on transition to closed' do
350
+ closed = false
351
+ @stream.on(:close) { closed = true }
352
+ @stream.receive RST_STREAM
353
+
354
+ expect(@stream.state).to eq :closed
355
+ expect(closed).to be_truthy
356
+ end
357
+ end
358
+
359
+ context 'half closed (remote)' do
360
+ before(:each) { @stream.receive HEADERS_END_STREAM }
361
+
362
+ it 'should raise STREAM_CLOSED error on reciept of frames' do
363
+ (FRAME_TYPES - [PRIORITY, RST_STREAM, WINDOW_UPDATE]).each do |frame|
364
+ expect do
365
+ @stream.dup.receive frame
366
+ end.to raise_error(StreamClosed)
367
+ end
368
+ end
369
+
370
+ it 'should transition to closed if END_STREAM flag is sent' do
371
+ [DATA, HEADERS].each do |frame|
372
+ s, f = @stream.dup, frame.deep_dup
373
+ f[:flags] = [:end_stream]
374
+
375
+ s.on(:close) { expect(s.state).to eq :closed }
376
+ s.send f
377
+ expect(s.state).to eq :closed
378
+ end
379
+ end
380
+
381
+ it 'should transition to closed if RST_STREAM is sent' do
382
+ @stream.close
383
+ expect(@stream.state).to eq :closed
384
+ end
385
+
386
+ it 'should transition to closed on reciept of RST_STREAM frame' do
387
+ @stream.receive RST_STREAM
388
+ expect(@stream.state).to eq :closed
389
+ end
390
+
391
+ it 'should ignore sent WINDOW_UPDATE frames' do
392
+ expect { @stream.send WINDOW_UPDATE.dup }.to_not raise_error
393
+ expect(@stream.state).to eq :half_closed_remote
394
+ end
395
+
396
+ it 'should increment remote_window on received WINDOW_UPDATE' do
397
+ expect { @stream.receive WINDOW_UPDATE }.to_not raise_error
398
+ expect(@stream.remote_window).to eq DEFAULT_FLOW_WINDOW + WINDOW_UPDATE[:increment]
399
+ end
400
+
401
+ it 'should reprioritize stream on sent PRIORITY' do
402
+ expect { @stream.send PRIORITY.dup }.to_not raise_error
403
+ expect(@stream.weight).to eq 20
404
+ end
405
+ it 'should reprioritize stream on received PRIORITY' do
406
+ expect { @stream.receive PRIORITY }.to_not raise_error
407
+ expect(@stream.weight).to eq 20
408
+ end
409
+
410
+ it 'should emit :half_close event on transition' do
411
+ order = []
412
+ stream = @client.new_stream
413
+ stream.on(:active) { order << :active }
414
+ stream.on(:half_close) { order << :half_close }
415
+
416
+ req = HEADERS.dup
417
+ req[:flags] = [:end_stream, :end_headers]
418
+
419
+ stream.receive req
420
+ expect(order).to eq [:active, :half_close]
421
+ end
422
+
423
+ it 'should emit :close event on close transition' do
424
+ closed = false
425
+ @stream.on(:close) { closed = true }
426
+ @stream.close
427
+
428
+ expect(@stream.state).to eq :closed
429
+ expect(closed).to be_truthy
430
+ end
431
+ end
432
+
433
+ context 'closed' do
434
+ context 'remote closed stream' do
435
+ before(:each) do
436
+ @stream.send HEADERS_END_STREAM.deep_dup # half closed local
437
+ @stream.receive HEADERS_END_STREAM # closed by remote
438
+ end
439
+
440
+ it 'should raise STREAM_CLOSED on attempt to send frames' do
441
+ (FRAME_TYPES - [PRIORITY, RST_STREAM]).each do |frame|
442
+ expect do
443
+ @stream.dup.send frame
444
+ end.to raise_error(StreamClosed)
445
+ end
446
+ end
447
+
448
+ it 'should raise STREAM_CLOSED on receipt of frame' do
449
+ (FRAME_TYPES - [PRIORITY, RST_STREAM, WINDOW_UPDATE]).each do |frame|
450
+ expect do
451
+ @stream.dup.receive frame
452
+ end.to raise_error(StreamClosed)
453
+ end
454
+ end
455
+
456
+ it 'should allow PRIORITY, RST_STREAM to be sent' do
457
+ expect { @stream.send PRIORITY.dup }.to_not raise_error
458
+ expect { @stream.send RST_STREAM.dup }.to_not raise_error
459
+ end
460
+
461
+ it 'should allow PRIORITY, RST_STREAM to be received' do
462
+ expect { @stream.receive PRIORITY }.to_not raise_error
463
+ expect { @stream.receive RST_STREAM }.to_not raise_error
464
+ end
465
+
466
+ it 'should reprioritize stream on sent PRIORITY' do
467
+ expect { @stream.send PRIORITY.dup }.to_not raise_error
468
+ expect(@stream.weight).to eq 20
469
+ end
470
+ it 'should reprioritize stream on received PRIORITY' do
471
+ expect { @stream.receive PRIORITY }.to_not raise_error
472
+ expect(@stream.weight).to eq 20
473
+ end
474
+
475
+ it 'should ignore received WINDOW_UPDATE frames' do
476
+ expect { @stream.receive WINDOW_UPDATE }.to_not raise_error
477
+ expect(@stream.state).to eq :closed
478
+ end
479
+ end
480
+
481
+ context 'local closed via RST_STREAM frame' do
482
+ before(:each) do
483
+ @stream.send HEADERS.deep_dup # open
484
+ @stream.send RST_STREAM.deep_dup # closed by local
485
+ end
486
+
487
+ it 'should ignore received frames' do
488
+ (FRAME_TYPES - [PUSH_PROMISE]).each do |frame|
489
+ expect do
490
+ cb = []
491
+ @stream.on(:data) { cb << :data }
492
+ @stream.on(:headers) { cb << :headers }
493
+ @stream.dup.receive frame.dup
494
+ expect(cb).to be_empty
495
+ end.to_not raise_error
496
+ end
497
+ end
498
+
499
+ # it "should transition to reserved remote on PUSH_PROMISE" do
500
+ # An endpoint might receive a PUSH_PROMISE frame after it sends
501
+ # RST_STREAM. PUSH_PROMISE causes a stream to become "reserved".
502
+ # ...
503
+ # We're auto RST'ing PUSH streams in connection class, hence
504
+ # skipping this transition for now.
505
+ # end
506
+ end
507
+
508
+ # FIXME: Isn't this test same as "half closed (local)"?
509
+ # context "local closed via END_STREAM flag" do
510
+ # before(:each) do
511
+ # @stream.send HEADERS # open
512
+ # @stream.send DATA # contains end_stream flag
513
+ # end
514
+
515
+ # it "should ignore received frames" do
516
+ # FRAME_TYPES.each do |frame|
517
+ # expect { @stream.dup.receive frame }.to_not raise_error
518
+ # end
519
+ # end
520
+ # end
521
+ end
522
+ end # end stream states
523
+
524
+ # TODO: add test cases to ensure on(:priority) emitted after close
525
+
526
+ context 'flow control' do
527
+ it 'should initialize to default flow control window' do
528
+ expect(@stream.remote_window).to eq DEFAULT_FLOW_WINDOW
529
+ end
530
+
531
+ it 'should update window size on DATA frames only' do
532
+ @stream.send HEADERS.deep_dup # go to open
533
+ expect(@stream.remote_window).to eq DEFAULT_FLOW_WINDOW
534
+
535
+ (FRAME_TYPES - [DATA, PING, GOAWAY, SETTINGS]).each do |frame|
536
+ s = @stream.dup
537
+ s.send frame.deep_dup
538
+ expect(s.remote_window).to eq DEFAULT_FLOW_WINDOW
539
+ end
540
+
541
+ @stream.send DATA.dup
542
+ expect(@stream.remote_window).to eq DEFAULT_FLOW_WINDOW - DATA[:payload].bytesize
543
+ end
544
+
545
+ it 'should update window size on receipt of WINDOW_UPDATE' do
546
+ @stream.send HEADERS.deep_dup
547
+ @stream.send DATA.dup
548
+ @stream.receive WINDOW_UPDATE
549
+
550
+ expect(@stream.remote_window).to eq(
551
+ DEFAULT_FLOW_WINDOW - DATA[:payload].bytesize + WINDOW_UPDATE[:increment],
552
+ )
553
+ end
554
+
555
+ it 'should observe session flow control' do
556
+ settings, data = SETTINGS.dup, DATA.dup
557
+ settings[:payload] = [[:settings_initial_window_size, 1000]]
558
+ settings[:stream] = 0
559
+
560
+ framer = Framer.new
561
+ @client << framer.generate(settings)
562
+
563
+ s1 = @client.new_stream
564
+ s1.send HEADERS.deep_dup
565
+ s1.send data.merge(payload: 'x' * 900, flags: [])
566
+ expect(s1.remote_window).to eq 100
567
+
568
+ s1.send data.merge(payload: 'x' * 200)
569
+ expect(s1.remote_window).to eq 0
570
+ expect(s1.buffered_amount).to eq 100
571
+
572
+ @client << framer.generate(WINDOW_UPDATE.merge(stream: s1.id, increment: 1000))
573
+ expect(s1.buffered_amount).to eq 0
574
+ expect(s1.remote_window).to eq 900
575
+ end
576
+ end
577
+
578
+ context 'client API' do
579
+ it '.reprioritize should emit PRIORITY frame' do
580
+ expect(@stream).to receive(:send) do |frame|
581
+ expect(frame[:type]).to eq :priority
582
+ expect(frame[:weight]).to eq 30
583
+ end
584
+
585
+ @stream.reprioritize weight: 30
586
+ end
587
+
588
+ it '.reprioritize should raise error if invoked by server' do
589
+ srv = Server.new
590
+ stream = srv.new_stream
591
+
592
+ expect { stream.reprioritize(weight: 10) }.to raise_error(InternalError)
593
+ end
594
+
595
+ it '.headers should emit HEADERS frames' do
596
+ payload = {
597
+ ':method' => 'GET',
598
+ ':scheme' => 'http',
599
+ ':host' => 'www.example.org',
600
+ ':path' => '/resource',
601
+ 'custom' => 'value',
602
+ }
603
+
604
+ expect(@stream).to receive(:send) do |frame|
605
+ expect(frame[:type]).to eq :headers
606
+ expect(frame[:payload]).to eq payload.to_a
607
+ expect(frame[:flags]).to eq [:end_headers]
608
+ end
609
+
610
+ @stream.headers(payload, end_stream: false, end_headers: true)
611
+ end
612
+
613
+ it '.data should emit DATA frames' do
614
+ expect(@stream).to receive(:send) do |frame|
615
+ expect(frame[:type]).to eq :data
616
+ expect(frame[:payload]).to eq 'text'
617
+ expect(frame[:flags]).to be_empty
618
+ end
619
+ @stream.data('text', end_stream: false)
620
+
621
+ expect(@stream).to receive(:send) do |frame|
622
+ expect(frame[:flags]).to eq [:end_stream]
623
+ end
624
+ @stream.data('text')
625
+ end
626
+
627
+ it '.data should split large DATA frames' do
628
+ data = 'x' * 16_384 * 2
629
+
630
+ want = [
631
+ { type: :data, flags: [], length: 16_384 },
632
+ { type: :data, flags: [], length: 16_384 },
633
+ { type: :data, flags: [:end_stream], length: 1 },
634
+ ]
635
+ want.each do |w|
636
+ expect(@stream).to receive(:send) do |frame|
637
+ expect(frame[:type]).to eq w[:type]
638
+ expect(frame[:flags]).to eq w[:flags]
639
+ expect(frame[:payload].length).to eq w[:length]
640
+ end
641
+ end
642
+
643
+ @stream.data(data + 'x')
644
+ end
645
+
646
+ it '.cancel should reset stream with cancel error code' do
647
+ expect(@stream).to receive(:send) do |frame|
648
+ expect(frame[:type]).to eq :rst_stream
649
+ expect(frame[:error]).to eq :cancel
650
+ end
651
+
652
+ @stream.cancel
653
+ end
654
+
655
+ it '.refuse should reset stream with refused stream error code' do
656
+ expect(@stream).to receive(:send) do |frame|
657
+ expect(frame[:type]).to eq :rst_stream
658
+ expect(frame[:error]).to eq :refused_stream
659
+ end
660
+
661
+ @stream.refuse
662
+ end
663
+ end
664
+
665
+ context 'server API' do
666
+ before(:each) do
667
+ @srv = Server.new
668
+ @frm = Framer.new
669
+
670
+ @client.on(:frame) { |bytes| @srv << bytes }
671
+ @client_stream = @client.new_stream
672
+ end
673
+
674
+ it 'should emit received headers via on(:headers)' do
675
+ headers, recv = [%w(header value)], nil
676
+ @srv.on(:stream) do |stream|
677
+ stream.on(:headers) { |h| recv = h }
678
+ end
679
+
680
+ @client_stream.headers(headers)
681
+ expect(recv).to eq headers
682
+ end
683
+
684
+ it 'should emit received payload via on(:data)' do
685
+ payload = 'some-payload'
686
+ @srv.on(:stream) do |stream|
687
+ stream.on(:data) do |recv|
688
+ expect(recv).to eq payload
689
+ end
690
+ end
691
+
692
+ @client_stream.headers('key' => 'value')
693
+ @client_stream.data(payload)
694
+ end
695
+
696
+ it 'should emit received priority parameters via on(:priority)' do
697
+ new_weight, new_dependency = 15, @client_stream.id + 2
698
+ callback_called = false
699
+ @srv.on(:stream) do |stream|
700
+ stream.on(:priority) do |pri|
701
+ callback_called = true
702
+ expect(pri.is_a?(Hash)).to be
703
+ expect(pri[:weight]).to eq new_weight
704
+ expect(pri[:dependency]).to eq new_dependency
705
+ end
706
+ end
707
+
708
+ @client_stream.headers('key' => 'value')
709
+ @client_stream.reprioritize(weight: new_weight, dependency: new_dependency)
710
+ expect(callback_called).to be
711
+ end
712
+
713
+ context 'push' do
714
+ before(:each) do
715
+ @srv.on(:frame) { |bytes| @client << bytes }
716
+ @srv.on(:stream) do |stream|
717
+ @server_stream = stream
718
+ end
719
+
720
+ @client_stream.headers('key' => 'value')
721
+ end
722
+
723
+ it '.promise should emit server initiated stream' do
724
+ push = nil
725
+ @server_stream.promise('key' => 'val') { |pstream| push = pstream }
726
+ expect(push.id).to eq 2
727
+ end
728
+
729
+ it '.promise push stream should have parent stream' do
730
+ push = nil
731
+ @server_stream.promise('key' => 'val') { |pstream| push = pstream }
732
+
733
+ expect(push.state).to eq :reserved_local
734
+ expect(push.parent.id).to eq @server_stream.id
735
+ end
736
+
737
+ context 'stream states' do
738
+ it 'server: active > half close > close' do
739
+ order = []
740
+ @server_stream.promise('key' => 'val') do |push|
741
+ stream = push
742
+
743
+ expect(push.state).to eq :reserved_local
744
+ order << :reserved
745
+
746
+ push.on(:active) { order << :active }
747
+ push.on(:half_close) { order << :half_close }
748
+ push.on(:close) { order << :close }
749
+
750
+ push.headers('key2' => 'val2')
751
+ push.send DATA.merge(stream: stream.id)
752
+ end
753
+
754
+ expect(order).to eq [:reserved, :active, :half_close, :close]
755
+ end
756
+
757
+ it 'client: headers > active > headers > .. > data > close' do
758
+ order, headers = [], []
759
+ @client.on(:promise) do |push|
760
+ order << :reserved
761
+
762
+ push.on(:active) { order << :active }
763
+ push.on(:data) { order << :data }
764
+ push.on(:half_close) { order << :half_close }
765
+ push.on(:close) { order << :close }
766
+
767
+ push.on(:headers) do |h|
768
+ order << :headers
769
+ headers += h
770
+ end
771
+
772
+ expect(push.id).to be_even
773
+ end
774
+
775
+ @server_stream.promise('key' => 'val') do |push|
776
+ push.headers('key2' => 'val2')
777
+ push.data('somedata')
778
+ end
779
+
780
+ expect(headers).to eq([%w(key val), %w(key2 val2)])
781
+ expect(order).to eq [
782
+ :reserved,
783
+ :headers,
784
+ :active,
785
+ :headers,
786
+ :half_close,
787
+ :data,
788
+ :close,
789
+ ]
790
+ end
791
+ end
792
+ end
793
+ end
794
+ end