sippy_cup 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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