sippy_cup 0.2.3 → 0.3.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,141 +1,890 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe SippyCup::Scenario do
4
+ include FakeFS::SpecHelpers
5
+
6
+ before do
7
+ Dir.mkdir("/tmp") unless Dir.exist?("/tmp")
8
+ Dir.chdir "/tmp"
9
+ end
10
+
4
11
  let(:default_args) { {source: '127.0.0.1:5060', destination: '10.0.0.1:5080'} }
12
+ let(:args) { {} }
13
+
14
+ subject(:scenario) { described_class.new 'Test', default_args.merge(args) }
5
15
 
6
- it %q{should create a media stream on initialization} do
16
+ it "creates a media stream on initialization" do
7
17
  SippyCup::Media.should_receive(:new).once
8
- SippyCup::Scenario.new 'Test', source: '127.0.0.1:5060', destination: '127.0.0.2:5061'
18
+ subject
9
19
  end
10
20
 
11
- it %q{should take a block to generate a scenario} do
12
- s = SippyCup::Scenario.new 'Test', default_args do
21
+ it "takes a block to generate a scenario" do
22
+ s = described_class.new 'Test', default_args do
13
23
  invite
14
24
  end
15
25
 
16
26
  s.to_xml.should =~ %r{INVITE sip:\[service\]@\[remote_ip\]:\[remote_port\] SIP/2.0}
17
27
  end
18
28
 
19
- it %q{should allow creating a blank scenario with no block} do
20
- s = SippyCup::Scenario.new 'Test', default_args
21
- s.invite
22
- s.to_xml.should =~ %r{INVITE sip:\[service\]@\[remote_ip\]:\[remote_port\] SIP/2.0}
29
+ it "allows creating a blank scenario with no block" do
30
+ subject.to_xml.should =~ %r{<scenario name="Test"/>}
23
31
  end
24
32
 
25
- describe '#wait_for_answer' do
26
- let(:scenario) { scenario = SippyCup::Scenario.new 'Test', source: '127.0.0.1:5061', destination: '127.0.0.1:5060' }
33
+ describe '#invite' do
34
+ it "sends an INVITE message" do
35
+ subject.invite
36
+
37
+ subject.to_xml.should match(%r{<send .*>})
38
+ subject.to_xml.should match(%r{INVITE})
39
+ end
40
+
41
+ it "allows setting options on the send instruction" do
42
+ subject.invite foo: 'bar'
43
+
44
+ subject.to_xml.should match(%r{<send foo="bar".*>})
45
+ end
46
+
47
+ it "defaults to retrans of 500" do
48
+ subject.invite
49
+ subject.to_xml.should match(%r{<send retrans="500".*>})
50
+ end
51
+
52
+ it "allows setting retrans" do
53
+ subject.invite retrans: 200
54
+ subject.to_xml.should match(%r{<send retrans="200".*>})
55
+ end
56
+
57
+ context "with extra headers specified" do
58
+ it "adds the headers to the end of the message" do
59
+ subject.invite headers: "Foo: <bar>\nBar: <baz>"
60
+ subject.to_xml.should match(%r{Foo: <bar>\nBar: <baz>})
61
+ end
62
+
63
+ it "only has one blank line between headers and SDP" do
64
+ subject.invite headers: "Foo: <bar>\n\n\n"
65
+ subject.to_xml.should match(%r{Foo: <bar>\n\nv=0})
66
+ end
67
+ end
68
+
69
+ context "with no extra headers" do
70
+ it "only has one blank line between headers and SDP" do
71
+ subject.invite
72
+ subject.to_xml.should match(%r{Content-Length: \[len\]\n\nv=0})
73
+ end
74
+ end
75
+
76
+ it "uses [media_port+1] as the RTCP port in the SDP" do
77
+ subject.invite
78
+ subject.to_xml.should match(%r{m=audio \[media_port\] RTP/AVP 0 101\n})
79
+ end
80
+
81
+ context "when a from user is specified" do
82
+ let(:args) { {from_user: 'frank'} }
83
+
84
+ it "includes the specified user in the From and Contact headers" do
85
+ subject.invite
86
+ subject.to_xml.should match(%r{From: "frank" <sip:frank@})
87
+ subject.to_xml.should match(%r{Contact: <sip:frank@})
88
+ end
89
+ end
90
+
91
+ context "when no from user is specified" do
92
+ it "uses a default of 'sipp' in the From and Contact headers" do
93
+ subject.invite
94
+ subject.to_xml.should match(%r{From: "sipp" <sip:sipp@})
95
+ subject.to_xml.should match(%r{Contact: <sip:sipp@})
96
+ end
97
+ end
98
+ end
99
+
100
+ describe "#register" do
101
+ it "sends a REGISTER message" do
102
+ subject.register 'frank'
103
+
104
+ subject.to_xml.should match(%r{<send .*>})
105
+ subject.to_xml.should match(%r{REGISTER})
106
+ end
107
+
108
+ it "allows setting options on the send instruction" do
109
+ subject.register 'frank', nil, foo: 'bar'
110
+ subject.to_xml.should match(%r{<send foo="bar".*>})
111
+ end
112
+
113
+ it "defaults to retrans of 500" do
114
+ subject.register 'frank'
115
+ subject.to_xml.should match(%r{<send retrans="500".*>})
116
+ end
117
+
118
+ it "allows setting retrans" do
119
+ subject.register 'frank', nil, retrans: 200
120
+ subject.to_xml.should match(%r{<send retrans="200".*>})
121
+ end
122
+
123
+ context "when a domain is provided" do
124
+ it "uses the specified user and domain" do
125
+ subject.register 'frank@foobar.com'
126
+ subject.to_xml.should match(%r{REGISTER sip:foobar.com})
127
+ subject.to_xml.should match(%r{From: <sip:frank@foobar.com})
128
+ subject.to_xml.should match(%r{To: <sip:frank@foobar.com})
129
+ subject.to_xml.should match(%r{Contact: <sip:sipp@\[local_ip\]})
130
+ end
131
+ end
132
+
133
+ context "when a domain is not provided" do
134
+ it "uses the remote IP" do
135
+ subject.register 'frank'
136
+ subject.to_xml.should match(%r{REGISTER sip:\[remote_ip\]})
137
+ subject.to_xml.should match(%r{From: <sip:frank@\[remote_ip\]})
138
+ subject.to_xml.should match(%r{To: <sip:frank@\[remote_ip\]})
139
+ subject.to_xml.should match(%r{Contact: <sip:sipp@\[local_ip\]})
140
+ end
141
+ end
142
+
143
+ context "when a password is provided" do
144
+ it "expects a 401 response" do
145
+ subject.register 'frank', 'abc123'
146
+ subject.to_xml.should match(%r{<recv response="401" auth="true" optional="false"/>})
147
+ end
148
+
149
+ it "adds authentication data to the REGISTER message" do
150
+ subject.register 'frank', 'abc123'
151
+ subject.to_xml.should match(%r{\[authentication username=frank password=abc123\]})
152
+ end
153
+ end
154
+ end
155
+
156
+ describe '#receive_trying' do
157
+ it "expects an optional 100" do
158
+ subject.receive_trying
159
+
160
+ scenario.to_xml.should match(%q{<recv response="100" optional="true"/>})
161
+ end
27
162
 
28
- it %q{should tell SIPp to optionally receive a SIP 100, 180 and 183 by default, while requiring a 200} do
163
+ it "allows passing options to the recv expectation" do
164
+ subject.receive_trying foo: 'bar'
165
+
166
+ scenario.to_xml.should match(%q{<recv foo="bar" response="100" optional="true"/>})
167
+ end
168
+
169
+ it "allows overriding options" do
170
+ subject.receive_trying optional: false
171
+
172
+ scenario.to_xml.should match(%q{<recv optional="false" response="100"/>})
173
+ end
174
+ end
175
+
176
+ describe '#receive_ringing' do
177
+ it "expects an optional 180" do
178
+ subject.receive_ringing
179
+
180
+ scenario.to_xml.should match(%q{<recv response="180" optional="true"/>})
181
+ end
182
+
183
+ it "allows passing options to the recv expectation" do
184
+ subject.receive_ringing foo: 'bar'
185
+
186
+ scenario.to_xml.should match(%q{<recv foo="bar" response="180" optional="true"/>})
187
+ end
188
+
189
+ it "allows overriding options" do
190
+ subject.receive_ringing optional: false
191
+
192
+ scenario.to_xml.should match(%q{<recv optional="false" response="180"/>})
193
+ end
194
+ end
195
+
196
+ describe '#receive_progress' do
197
+ it "expects an optional 183" do
198
+ subject.receive_progress
199
+
200
+ scenario.to_xml.should match(%q{<recv response="183" optional="true"/>})
201
+ end
202
+
203
+ it "allows passing options to the recv expectation" do
204
+ subject.receive_progress foo: 'bar'
205
+
206
+ scenario.to_xml.should match(%q{<recv foo="bar" response="183" optional="true"/>})
207
+ end
208
+
209
+ it "allows overriding options" do
210
+ subject.receive_progress optional: false
211
+
212
+ scenario.to_xml.should match(%q{<recv optional="false" response="183"/>})
213
+ end
214
+ end
215
+
216
+ describe '#receive_answer' do
217
+ it "expects a 200 with rrs and rtd true" do
218
+ subject.receive_answer
219
+
220
+ scenario.to_xml.should match(%q{<recv response="200" rrs="true" rtd="true"/>})
221
+ end
222
+
223
+ it "allows passing options to the recv expectation" do
224
+ subject.receive_answer foo: 'bar'
225
+
226
+ scenario.to_xml.should match(%q{<recv response="200" rrs="true" rtd="true" foo="bar"/>})
227
+ end
228
+
229
+ it "allows overriding options" do
230
+ subject.receive_answer rtd: false
231
+
232
+ scenario.to_xml.should match(%q{<recv response="200" rrs="true" rtd="false"/>})
233
+ end
234
+ end
235
+
236
+ describe '#ack_answer' do
237
+ it "sends an ACK message" do
238
+ subject.ack_answer
239
+
240
+ subject.to_xml.should match(%r{<send>})
241
+ subject.to_xml.should match(%r{ACK})
242
+ end
243
+
244
+ it "allows setting options on the send instruction" do
245
+ subject.ack_answer foo: 'bar'
246
+ subject.to_xml.should match(%r{<send foo="bar".*>})
247
+ end
248
+
249
+ it "starts the PCAP media" do
250
+ subject.ack_answer
251
+
252
+ subject.to_xml.should match(%r{<nop>\n.*<action>\n.*<exec play_pcap_audio="/tmp/test.pcap"/>\n.*</action>\n.*</nop>})
253
+ end
254
+
255
+ context "when a from user is specified" do
256
+ let(:args) { {from_user: 'frank'} }
257
+
258
+ it "includes the specified user in the From and Contact headers" do
259
+ subject.ack_answer
260
+ subject.to_xml.should match(%r{From: "frank" <sip:frank@})
261
+ subject.to_xml.should match(%r{Contact: <sip:frank@})
262
+ end
263
+ end
264
+
265
+ context "when no from user is specified" do
266
+ it "uses a default of 'sipp' in the From and Contact headers" do
267
+ subject.ack_answer
268
+ subject.to_xml.should match(%r{From: "sipp" <sip:sipp@})
269
+ subject.to_xml.should match(%r{Contact: <sip:sipp@})
270
+ end
271
+ end
272
+ end
273
+
274
+ describe '#wait_for_answer' do
275
+ it "tells SIPp to optionally receive a SIP 100, 180 and 183 by default, while requiring a 200" do
29
276
  scenario.wait_for_answer
30
277
 
31
278
  xml = scenario.to_xml
32
- xml.should =~ /recv response="100".*optional="true"/
33
- xml.should =~ /recv response="180".*optional="true"/
34
- xml.should =~ /recv response="183".*optional="true"/
279
+ xml.should =~ /recv optional="true".*response="100"/
280
+ xml.should =~ /recv optional="true".*response="180"/
281
+ xml.should =~ /recv optional="true".*response="183"/
35
282
  xml.should =~ /recv response="200"/
36
- xml.should_not =~ /recv response="200".*optional="true"/
283
+ xml.should_not =~ /recv optional="true".*response="200"/
37
284
  end
38
285
 
39
- it %q{should pass through additional options} do
286
+ it "passes through additional options" do
40
287
  scenario.wait_for_answer foo: 'bar'
41
288
 
42
289
  xml = scenario.to_xml
43
- xml.should =~ /recv response="100".*foo="bar"/
44
- xml.should =~ /recv response="180".*foo="bar"/
45
- xml.should =~ /recv response="183".*foo="bar"/
46
- xml.should =~ /recv response="200".*foo="bar"/
290
+ xml.should =~ /recv .*foo="bar".*response="100"/
291
+ xml.should =~ /recv .*foo="bar".*response="180"/
292
+ xml.should =~ /recv .*foo="bar".*response="183"/
293
+ xml.should =~ /recv .*response="200" .*foo="bar"/
294
+ end
295
+ end
296
+
297
+ describe '#send_bye' do
298
+ it "sends a BYE message" do
299
+ subject.send_bye
300
+
301
+ subject.to_xml.should match(%r{<send>})
302
+ subject.to_xml.should match(%r{BYE})
303
+ end
304
+
305
+ it "allows setting options on the send instruction" do
306
+ subject.send_bye foo: 'bar'
307
+ subject.to_xml.should match(%r{<send foo="bar".*>})
308
+ end
309
+
310
+ context "when a from user is specified" do
311
+ let(:args) { {from_user: 'frank'} }
312
+
313
+ it "includes the specified user in the From and Contact headers" do
314
+ subject.send_bye
315
+ subject.to_xml.should match(%r{From: "frank" <sip:frank@})
316
+ subject.to_xml.should match(%r{Contact: <sip:frank@})
317
+ end
318
+ end
319
+
320
+ context "when no from user is specified" do
321
+ it "uses a default of 'sipp' in the From and Contact headers" do
322
+ subject.send_bye
323
+ subject.to_xml.should match(%r{From: "sipp" <sip:sipp@})
324
+ subject.to_xml.should match(%r{Contact: <sip:sipp@})
325
+ end
326
+ end
327
+ end
328
+
329
+ describe '#receive_bye' do
330
+ it "expects a BYE" do
331
+ subject.receive_bye
332
+
333
+ scenario.to_xml.should match(%q{<recv request="BYE"/>})
334
+ end
335
+
336
+ it "allows passing options to the recv expectation" do
337
+ subject.receive_bye foo: 'bar'
338
+
339
+ scenario.to_xml.should match(%q{<recv foo="bar" request="BYE"/>})
340
+ end
341
+ end
342
+
343
+ describe '#ack_bye' do
344
+ it "sends a 200 OK" do
345
+ subject.ack_bye
346
+
347
+ subject.to_xml.should match(%r{<send>})
348
+ subject.to_xml.should match(%r{SIP/2.0 200 OK})
349
+ end
350
+
351
+ it "allows setting options on the send instruction" do
352
+ subject.ack_bye foo: 'bar'
353
+ subject.to_xml.should match(%r{<send foo="bar".*>})
354
+ end
355
+
356
+ context "when a from user is specified" do
357
+ let(:args) { {from_user: 'frank'} }
358
+
359
+ it "includes the specified user in the Contact header" do
360
+ subject.ack_bye
361
+ subject.to_xml.should match(%r{Contact: <sip:frank@})
362
+ end
363
+ end
364
+
365
+ context "when no from user is specified" do
366
+ it "uses a default of 'sipp' in the Contact header" do
367
+ subject.ack_bye
368
+ subject.to_xml.should match(%r{Contact: <sip:sipp@})
369
+ end
370
+ end
371
+ end
372
+
373
+ describe '#wait_for_hangup' do
374
+ it "expects a BYE and acks it" do
375
+ subject.receive_bye foo: 'bar'
376
+
377
+ scenario.to_xml.should match(%q{<recv foo="bar" request="BYE"/>})
378
+ scenario.to_xml.should match(%q{<recv foo="bar" request="BYE"/>})
379
+ end
380
+ end
381
+
382
+ describe '#ack_bye' do
383
+ it "sends a 200 OK" do
384
+ subject.ack_bye
385
+
386
+ subject.to_xml.should match(%r{<send>})
387
+ subject.to_xml.should match(%r{SIP/2.0 200 OK})
47
388
  end
48
389
  end
49
390
 
50
391
  describe 'media-dependent operations' do
51
392
  let(:media) { double :media }
52
- let(:scenario) do
393
+ before do
53
394
  SippyCup::Media.should_receive(:new).once.and_return media
54
- scenario = SippyCup::Scenario.new 'Test', source: '127.0.0.1:5061', destination: '127.0.0.1:5060'
395
+ media.stub :<<
55
396
  end
56
397
 
57
- it %q{should create the proper amount of silent audio'} do
58
- media.should_receive(:<<).once.with 'silence:5000'
59
- scenario.sleep 5
398
+ describe '#sleep' do
399
+ it "creates the proper amount of silent audio'" do
400
+ media.should_receive(:<<).once.with 'silence:5000'
401
+ scenario.sleep 5
402
+ end
403
+
404
+ it "should insert a pause into the scenario" do
405
+ scenario.sleep 5
406
+ scenario.to_xml.should match(%r{<pause milliseconds="5000"/>})
407
+ end
408
+
409
+ context "when passed fractional seconds" do
410
+ it "creates the proper amount of silent audio" do
411
+ media.should_receive(:<<).once.with 'silence:500'
412
+ scenario.sleep '0.5'
413
+ end
414
+
415
+ it "should insert a pause into the scenario" do
416
+ scenario.sleep 0.5
417
+ scenario.to_xml.should match(%r{<pause milliseconds="500"/>})
418
+ end
419
+ end
60
420
  end
61
421
 
62
- it %q{should create the requested DTMF string'} do
63
- media.should_receive(:<<).ordered.with 'dtmf:1'
64
- media.should_receive(:<<).ordered.with 'silence:250'
65
- media.should_receive(:<<).ordered.with 'dtmf:3'
66
- media.should_receive(:<<).ordered.with 'silence:250'
67
- media.should_receive(:<<).ordered.with 'dtmf:6'
68
- media.should_receive(:<<).ordered.with 'silence:250'
69
- scenario.send_digits '136'
422
+ describe '#send_digits' do
423
+ it "creates the requested DTMF string in media, with 250ms pauses between" do
424
+ media.should_receive(:<<).ordered.with 'dtmf:1'
425
+ media.should_receive(:<<).ordered.with 'silence:250'
426
+ media.should_receive(:<<).ordered.with 'dtmf:3'
427
+ media.should_receive(:<<).ordered.with 'silence:250'
428
+ media.should_receive(:<<).ordered.with 'dtmf:6'
429
+ media.should_receive(:<<).ordered.with 'silence:250'
430
+ scenario.send_digits '136'
431
+ end
432
+
433
+ it "should insert a pause into the scenario to cover the DTMF duration (250ms) and the pause" do
434
+ scenario.send_digits '136'
435
+ scenario.to_xml.should match(%r{<pause milliseconds="1500"/>})
436
+ end
70
437
  end
71
438
  end
72
439
 
73
- describe "#register" do
74
- let(:scenario) { SippyCup::Scenario.new 'Test', source: '127.0.0.1:5061', destination: '127.0.0.1:5060' }
440
+ describe "#compile!" do
441
+ context "when a filename is not provided" do
442
+ it "writes the scenario XML to disk at name.xml" do
443
+ scenario.invite
444
+
445
+ scenario.compile!
446
+
447
+ File.read("/tmp/test.xml").should == scenario.to_xml
448
+ end
449
+
450
+ it "writes the PCAP media to disk at name.pcap" do
451
+ scenario.send_digits '123'
452
+
453
+ scenario.compile!
454
+
455
+ File.read("/tmp/test.pcap").should_not be_empty
456
+ end
457
+
458
+ it "returns the path to the scenario file" do
459
+ scenario.compile!.should == "/tmp/test.xml"
460
+ end
461
+ end
462
+
463
+ context "when a filename is provided" do
464
+ let(:args) { {filename: 'foobar'} }
465
+
466
+ it "writes the scenario XML to disk at filename.xml" do
467
+ scenario.invite
468
+
469
+ scenario.compile!
470
+
471
+ File.read("/tmp/foobar.xml").should == scenario.to_xml
472
+ end
473
+
474
+ it "writes the PCAP media to disk at filename.pcap" do
475
+ scenario.send_digits '123'
476
+
477
+ scenario.compile!
75
478
 
76
- it %q{should only call #register_message if only user is passed} do
77
- scenario.should_receive(:register_message).with 'foo', domain: "example.com"
78
- scenario.should_not_receive(:register_auth)
79
- scenario.register 'foo@example.com'
479
+ File.read("/tmp/foobar.pcap").should_not be_empty
480
+ end
481
+
482
+ it "returns the path to the scenario file" do
483
+ scenario.compile!.should == "/tmp/foobar.xml"
484
+ end
80
485
  end
486
+ end
81
487
 
82
- it %q{should call #register_auth if user and password are passed} do
83
- scenario.should_receive(:register_auth).with 'sally', 'seekrut', domain: "[remote_ip]"
84
- scenario.register 'sally', 'seekrut'
488
+ describe "#to_tmpfiles" do
489
+ before { scenario.invite }
490
+
491
+ it "writes the scenario XML to a Tempfile and returns it" do
492
+ files = scenario.to_tmpfiles
493
+ files[:scenario].should be_a(Tempfile)
494
+ files[:scenario].read.should eql(scenario.to_xml)
85
495
  end
86
496
 
87
- it %q{should not modify the passed in user if a domain is given} do
88
- scenario.register 'foo@example.com'
497
+ it "allows the scenario XML to be read from disk independently" do
498
+ files = scenario.to_tmpfiles
499
+ File.read(files[:scenario].path).should eql(scenario.to_xml)
500
+ end
89
501
 
90
- xml = scenario.to_xml
91
- xml.should =~ %r{foo@example\.com}
502
+ it "writes the PCAP media to a Tempfile and returns it" do
503
+ files = scenario.to_tmpfiles
504
+ files[:media].should be_a(Tempfile)
505
+ files[:media].read.should_not be_empty
92
506
  end
93
507
 
94
- it %q{should interpolate the target IP if no domain is given} do
95
- scenario.register 'sally'
508
+ it "allows the PCAP media to be read from disk independently" do
509
+ files = scenario.to_tmpfiles
510
+ File.read(files[:media].path).should_not be_empty
511
+ end
512
+ end
96
513
 
97
- xml = scenario.to_xml
98
- xml.should =~ %r{sally@\[remote_ip\]}
514
+ describe "#build" do
515
+ let(:scenario_xml) do <<-END
516
+ <?xml version="1.0"?>
517
+ <scenario name="Test">
518
+ <send retrans="500">
519
+ <![CDATA[
520
+ INVITE sip:[service]@[remote_ip]:[remote_port] SIP/2.0
521
+ Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
522
+ From: "sipp" <sip:sipp@[local_ip]>;tag=[call_number]
523
+ To: <sip:[service]@[remote_ip]:[remote_port]>
524
+ Call-ID: [call_id]
525
+ CSeq: [cseq] INVITE
526
+ Contact: <sip:sipp@[local_ip]:[local_port];transport=[transport]>
527
+ Max-Forwards: 100
528
+ User-Agent: SIPp/sippy_cup
529
+ Content-Type: application/sdp
530
+ Content-Length: [len]
531
+
532
+ v=0
533
+ o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
534
+ s=-
535
+ c=IN IP[media_ip_type] [media_ip]
536
+ t=0 0
537
+ m=audio [media_port] RTP/AVP 0 101
538
+ a=rtpmap:0 PCMU/8000
539
+ a=rtpmap:101 telephone-event/8000
540
+ a=fmtp:101 0-15
541
+ ]]>
542
+ </send>
543
+ <recv optional="true" response="100"/>
544
+ <recv optional="true" response="180"/>
545
+ <recv optional="true" response="183"/>
546
+ <recv response="200" rrs="true" rtd="true"/>
547
+ <send>
548
+ <![CDATA[
549
+ ACK [next_url] SIP/2.0
550
+ Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
551
+ From: "sipp" <sip:sipp@[local_ip]>;tag=[call_number]
552
+ [last_To:]
553
+ Call-ID: [call_id]
554
+ CSeq: [cseq] ACK
555
+ Contact: <sip:sipp@[local_ip]:[local_port];transport=[transport]>
556
+ Max-Forwards: 100
557
+ User-Agent: SIPp/sippy_cup
558
+ Content-Length: 0
559
+ [routes]
560
+ ]]>
561
+ </send>
562
+ <nop>
563
+ <action>
564
+ <exec play_pcap_audio="/tmp/test.pcap"/>
565
+ </action>
566
+ </nop>
567
+ <recv request="BYE"/>
568
+ <send>
569
+ <![CDATA[
570
+ SIP/2.0 200 OK
571
+ [last_Via:]
572
+ [last_From:]
573
+ [last_To:]
574
+ [last_Call-ID:]
575
+ [last_CSeq:]
576
+ Contact: <sip:sipp@[local_ip]:[local_port];transport=[transport]>
577
+ Max-Forwards: 100
578
+ User-Agent: SIPp/sippy_cup
579
+ Content-Length: 0
580
+ [routes]
581
+ ]]>
582
+ </send>
583
+ </scenario>
584
+ END
99
585
  end
100
586
 
101
- it %q{should add an auth to registers which specify a password} do
102
- scenario.register 'foo@example.com', 'seekrut'
587
+ context "with a valid steps definition" do
588
+ let(:steps) { ['invite', 'wait_for_answer', 'ack_answer', 'wait_for_hangup'] }
103
589
 
104
- xml = scenario.to_xml
105
- xml.should =~ %r{recv response="401" optional="false" auth="true"}
106
- xml.should =~ %r{\[authentication username=foo password=seekrut\]}
590
+ it "runs each step" do
591
+ subject.build(steps)
592
+ subject.to_xml.should == scenario_xml
593
+ end
594
+ end
595
+
596
+ context "with an invalid steps definition" do
597
+ let(:steps) { ["send_digits 'b'"] }
598
+
599
+ it "doesn't raise errors" do
600
+ expect { subject.build(steps) }.to_not raise_error
601
+ end
107
602
  end
108
603
  end
109
604
 
110
- describe "#parse_user" do
111
- let(:scenario) { SippyCup::Scenario.new 'Test', source: '127.0.0.1:5061', destination: '127.0.0.1:5060' }
605
+ describe ".from_manifest" do
606
+ let(:specs_from) { 'specs' }
112
607
 
113
- context "sip: prefix" do
608
+ let(:scenario_yaml) do <<-END
609
+ name: spec scenario
610
+ source: 192.0.2.15
611
+ destination: 192.0.2.200
612
+ max_concurrent: 10
613
+ calls_per_second: 5
614
+ number_of_calls: 20
615
+ from_user: #{specs_from}
616
+ steps:
617
+ - invite
618
+ - wait_for_answer
619
+ - ack_answer
620
+ - sleep 3
621
+ - send_digits '3125551234'
622
+ - sleep 5
623
+ - send_digits '#'
624
+ - wait_for_hangup
625
+ END
626
+ end
627
+
628
+ let(:scenario_xml) do <<-END
629
+ <?xml version="1.0"?>
630
+ <scenario name="spec scenario">
631
+ <send retrans="500">
632
+ <![CDATA[
633
+ INVITE sip:[service]@[remote_ip]:[remote_port] SIP/2.0
634
+ Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
635
+ From: "#{specs_from}" <sip:#{specs_from}@[local_ip]>;tag=[call_number]
636
+ To: <sip:[service]@[remote_ip]:[remote_port]>
637
+ Call-ID: [call_id]
638
+ CSeq: [cseq] INVITE
639
+ Contact: <sip:#{specs_from}@[local_ip]:[local_port];transport=[transport]>
640
+ Max-Forwards: 100
641
+ User-Agent: SIPp/sippy_cup
642
+ Content-Type: application/sdp
643
+ Content-Length: [len]
644
+
645
+ v=0
646
+ o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
647
+ s=-
648
+ c=IN IP[media_ip_type] [media_ip]
649
+ t=0 0
650
+ m=audio [media_port] RTP/AVP 0 101
651
+ a=rtpmap:0 PCMU/8000
652
+ a=rtpmap:101 telephone-event/8000
653
+ a=fmtp:101 0-15
654
+ ]]>
655
+ </send>
656
+ <recv optional="true" response="100"/>
657
+ <recv optional="true" response="180"/>
658
+ <recv optional="true" response="183"/>
659
+ <recv response="200" rrs="true" rtd="true"/>
660
+ <send>
661
+ <![CDATA[
662
+ ACK [next_url] SIP/2.0
663
+ Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
664
+ From: "#{specs_from}" <sip:#{specs_from}@[local_ip]>;tag=[call_number]
665
+ [last_To:]
666
+ Call-ID: [call_id]
667
+ CSeq: [cseq] ACK
668
+ Contact: <sip:#{specs_from}@[local_ip]:[local_port];transport=[transport]>
669
+ Max-Forwards: 100
670
+ User-Agent: SIPp/sippy_cup
671
+ Content-Length: 0
672
+ [routes]
673
+ ]]>
674
+ </send>
675
+ <nop>
676
+ <action>
677
+ <exec play_pcap_audio="/tmp/spec_scenario.pcap"/>
678
+ </action>
679
+ </nop>
680
+ <pause milliseconds="3000"/>
681
+ <pause milliseconds="5000"/>
682
+ <pause milliseconds="5000"/>
683
+ <pause milliseconds="500"/>
684
+ <recv request="BYE"/>
685
+ <send>
686
+ <![CDATA[
687
+ SIP/2.0 200 OK
688
+ [last_Via:]
689
+ [last_From:]
690
+ [last_To:]
691
+ [last_Call-ID:]
692
+ [last_CSeq:]
693
+ Contact: <sip:#{specs_from}@[local_ip]:[local_port];transport=[transport]>
694
+ Max-Forwards: 100
695
+ User-Agent: SIPp/sippy_cup
696
+ Content-Length: 0
697
+ [routes]
698
+ ]]>
699
+ </send>
700
+ </scenario>
701
+ END
702
+ end
114
703
 
115
- it %q{should return user and domain for addresses in the sip:user@domain:port format} do
116
- scenario.parse_user('sip:foo@example.com:1337').should == ['foo', 'example.com']
704
+ let(:override_options) { { number_of_calls: 10 } }
705
+
706
+ it "generates the correct XML" do
707
+ scenario = described_class.from_manifest(scenario_yaml)
708
+ scenario.to_xml.should == scenario_xml
709
+ end
710
+
711
+ it "sets the proper options" do
712
+ scenario = described_class.from_manifest(scenario_yaml)
713
+ scenario.scenario_options.should == {
714
+ 'name' => 'spec scenario',
715
+ 'source' => '192.0.2.15',
716
+ 'destination' => '192.0.2.200',
717
+ 'max_concurrent' => 10,
718
+ 'calls_per_second' => 5,
719
+ 'number_of_calls' => 20,
720
+ 'from_user' => "#{specs_from}"
721
+ }
722
+ end
723
+
724
+ context "when the :scenario key is provided in the manifest" do
725
+ let(:scenario_path) { File.expand_path('scenario.xml', File.join(File.dirname(__FILE__), '..', 'fixtures')) }
726
+ let(:scenario_yaml) do <<-END
727
+ name: spec scenario
728
+ source: 192.0.2.15
729
+ destination: 192.0.2.200
730
+ max_concurrent: 10
731
+ calls_per_second: 5
732
+ number_of_calls: 20
733
+ from_user: #{specs_from}
734
+ scenario: #{scenario_path}
735
+ END
736
+ end
737
+
738
+ before { FakeFS.deactivate! }
739
+
740
+ it "creates an XMLScenario with the scenario XML and nil media" do
741
+ scenario = described_class.from_manifest(scenario_yaml)
742
+ scenario.should be_a(SippyCup::XMLScenario)
743
+ scenario.to_xml.should == File.read(scenario_path)
744
+ end
745
+
746
+ context "and the :media key is provided" do
747
+ let(:media_path) { File.expand_path('dtmf_2833_1.pcap', File.join(File.dirname(__FILE__), '..', 'fixtures')) }
748
+ let(:scenario_yaml) do <<-END
749
+ name: spec scenario
750
+ source: 192.0.2.15
751
+ destination: 192.0.2.200
752
+ max_concurrent: 10
753
+ calls_per_second: 5
754
+ number_of_calls: 20
755
+ from_user: #{specs_from}
756
+ scenario: #{scenario_path}
757
+ media: #{media_path}
758
+ END
759
+ end
760
+
761
+ it "creates an XMLScenario with the scenario XML and media from the filesystem" do
762
+ scenario = described_class.from_manifest(scenario_yaml)
763
+
764
+ media = File.read(media_path, mode: 'rb')
765
+
766
+ files = scenario.to_tmpfiles
767
+ files[:media].read.should eql(media)
768
+ end
769
+ end
770
+ end
771
+
772
+ context "without a name specified" do
773
+ let(:scenario_yaml) do <<-END
774
+ source: 192.0.2.15
775
+ destination: 192.0.2.200
776
+ max_concurrent: 10
777
+ calls_per_second: 5
778
+ number_of_calls: 20
779
+ from_user: #{specs_from}
780
+ steps:
781
+ - invite
782
+ - wait_for_answer
783
+ - ack_answer
784
+ - sleep 3
785
+ - send_digits '3125551234'
786
+ - sleep 5
787
+ - send_digits '#'
788
+ - wait_for_hangup
789
+ END
790
+ end
791
+
792
+ it "should default to 'My Scenario'" do
793
+ scenario = described_class.from_manifest(scenario_yaml)
794
+ scenario.scenario_options[:name].should == 'My Scenario'
795
+ end
796
+ end
797
+
798
+ context "with an input filename specified" do
799
+ context "and a name in the manifest" do
800
+ it "uses the name from the manifest" do
801
+ scenario = described_class.from_manifest(scenario_yaml, input_filename: '/tmp/foobar.yml')
802
+ scenario.scenario_options[:name].should == 'spec scenario'
803
+ end
117
804
  end
118
805
 
119
- it %q{should return user and domain for addresses in the sip:user@domain format} do
120
- scenario.parse_user('sip:foo@example.com').should == ['foo', 'example.com']
806
+ context "and no name in the manifest" do
807
+ let(:scenario_yaml) do <<-END
808
+ source: 192.0.2.15
809
+ destination: 192.0.2.200
810
+ max_concurrent: 10
811
+ calls_per_second: 5
812
+ number_of_calls: 20
813
+ from_user: #{specs_from}
814
+ steps:
815
+ - invite
816
+ - wait_for_answer
817
+ - ack_answer
818
+ - sleep 3
819
+ - send_digits '3125551234'
820
+ - sleep 5
821
+ - send_digits '#'
822
+ - wait_for_hangup
823
+ END
824
+ end
825
+
826
+ it "uses the input filename" do
827
+ scenario = described_class.from_manifest(scenario_yaml, input_filename: '/tmp/foobar.yml')
828
+ scenario.scenario_options[:name].should == 'foobar'
829
+ end
121
830
  end
831
+ end
122
832
 
123
- it %q{should return user and [remote_ip] for addresses in the sip:user format} do
124
- scenario.parse_user('sip:foo').should == ['foo', '[remote_ip]']
833
+ context "overriding some value" do
834
+ let(:specs_from) { 'other_user' }
835
+
836
+ it "overrides keys with values from the options hash" do
837
+ scenario = described_class.from_manifest(scenario_yaml, override_options)
838
+ scenario.to_xml.should == scenario_xml
839
+ end
840
+
841
+ it "sets the proper options" do
842
+ scenario = described_class.from_manifest(scenario_yaml, override_options)
843
+ scenario.scenario_options.should == {
844
+ 'name' => 'spec scenario',
845
+ 'source' => '192.0.2.15',
846
+ 'destination' => '192.0.2.200',
847
+ 'max_concurrent' => 10,
848
+ 'calls_per_second' => 5,
849
+ 'number_of_calls' => override_options[:number_of_calls],
850
+ 'from_user' => "#{specs_from}"
851
+ }
125
852
  end
126
853
  end
127
854
 
128
- context "no prefix" do
129
- it %q{should return user and domain for addresses in the user@domain:port format} do
130
- scenario.parse_user('foo@example.com:1337').should == ['foo', 'example.com']
855
+ context "with an invalid scenario" do
856
+ let(:scenario_yaml) do <<-END
857
+ name: spec scenario
858
+ source: 192.0.2.15
859
+ destination: 192.0.2.200
860
+ max_concurrent: 10
861
+ calls_per_second: 5
862
+ number_of_calls: 20
863
+ from_user: #{specs_from}
864
+ steps:
865
+ - invite
866
+ - wait_for_answer
867
+ - ack_answer
868
+ - sleep 3
869
+ - send_digits 'abc'
870
+ - sleep 5
871
+ - send_digits '#'
872
+ - wait_for_hangup
873
+ END
874
+ end
875
+
876
+ it "does not raise errors" do
877
+ expect { SippyCup::Scenario.from_manifest(scenario_yaml) }.to_not raise_error
131
878
  end
132
879
 
133
- it %q{should return user and domain for addresses in the user@domain format} do
134
- scenario.parse_user('foo@example.com').should == ['foo', 'example.com']
880
+ it "sets the validity of the scenario" do
881
+ scenario = SippyCup::Scenario.from_manifest(scenario_yaml)
882
+ scenario.should_not be_valid
135
883
  end
136
884
 
137
- it %q{should return user and [remote_ip] for a standalone username} do
138
- scenario.parse_user('sally').should == ['sally', '[remote_ip]']
885
+ it "sets the error messages for the scenario" do
886
+ scenario = SippyCup::Scenario.from_manifest(scenario_yaml)
887
+ scenario.errors.should == [{step: 5, message: "send_digits 'abc': Invalid DTMF digit requested: a"}]
139
888
  end
140
889
  end
141
890
  end