ruby-asterisk 0.1.0 → 1.0.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.
- checksums.yaml +5 -5
- data/.claude/skills/ruby-asterisk-agi/SKILL.md +317 -0
- data/.claude/skills/ruby-asterisk-ami/SKILL.md +187 -0
- data/.claude/skills/ruby-asterisk-ari/SKILL.md +174 -0
- data/.claude/skills/ruby-asterisk-ari-websocket/SKILL.md +148 -0
- data/.github/workflows/ci.yml +79 -0
- data/.gitignore +50 -3
- data/.rubocop.yml +84 -0
- data/CHANGELOG.md +52 -0
- data/Gemfile +3 -1
- data/LICENCE +21 -0
- data/README.md +454 -70
- data/Rakefile +3 -1
- data/lib/ruby-asterisk/agi/protocol.rb +160 -0
- data/lib/ruby-asterisk/agi/server.rb +108 -0
- data/lib/ruby-asterisk/agi/session.rb +187 -0
- data/lib/ruby-asterisk/ami/client.rb +135 -0
- data/lib/ruby-asterisk/ami/commands/channel.rb +55 -0
- data/lib/ruby-asterisk/ami/commands/conference.rb +37 -0
- data/lib/ruby-asterisk/ami/commands/extension.rb +17 -0
- data/lib/ruby-asterisk/ami/commands/mailbox.rb +21 -0
- data/lib/ruby-asterisk/ami/commands/monitor.rb +33 -0
- data/lib/ruby-asterisk/ami/commands/queue.rb +39 -0
- data/lib/ruby-asterisk/ami/commands/sip.rb +25 -0
- data/lib/ruby-asterisk/ami/commands/system.rb +50 -0
- data/lib/ruby-asterisk/ami/event.rb +22 -0
- data/lib/ruby-asterisk/ami/event_list_aggregation.rb +82 -0
- data/lib/ruby-asterisk/ami/parser.rb +60 -0
- data/lib/ruby-asterisk/ami/promise.rb +108 -0
- data/lib/ruby-asterisk/ami/reactor.rb +162 -0
- data/lib/ruby-asterisk/ari/client.rb +94 -0
- data/lib/ruby-asterisk/ari/resources/base.rb +23 -0
- data/lib/ruby-asterisk/ari/resources/bridge.rb +22 -0
- data/lib/ruby-asterisk/ari/resources/channel.rb +26 -0
- data/lib/ruby-asterisk/ari/resources/collection.rb +27 -0
- data/lib/ruby-asterisk/ari/resources/endpoint.rb +22 -0
- data/lib/ruby-asterisk/ari/resources/playback.rb +18 -0
- data/lib/ruby-asterisk/ari/websocket/connection.rb +143 -0
- data/lib/ruby-asterisk/ari/websocket/event_handlers.rb +80 -0
- data/lib/ruby-asterisk/ari/websocket/heartbeat.rb +65 -0
- data/lib/ruby-asterisk/ari/websocket/reconnect.rb +54 -0
- data/lib/ruby-asterisk/ari/websocket/socket_adapter.rb +23 -0
- data/lib/ruby-asterisk/ari/websocket.rb +155 -0
- data/lib/ruby-asterisk/compat.rb +7 -0
- data/lib/ruby-asterisk/error.rb +10 -0
- data/lib/ruby-asterisk/parsing_constants.rb +100 -0
- data/lib/ruby-asterisk/request.rb +38 -20
- data/lib/ruby-asterisk/response.rb +50 -137
- data/lib/ruby-asterisk/response_builder.rb +18 -0
- data/lib/ruby-asterisk/response_parser.rb +43 -0
- data/lib/ruby-asterisk/version.rb +4 -2
- data/lib/ruby-asterisk.rb +5 -147
- data/ruby-asterisk.gemspec +26 -17
- data/spec/ruby-asterisk/agi/protocol_spec.rb +239 -0
- data/spec/ruby-asterisk/agi/server_spec.rb +199 -0
- data/spec/ruby-asterisk/agi/session_spec.rb +293 -0
- data/spec/ruby-asterisk/ami/async_client_spec.rb +256 -0
- data/spec/ruby-asterisk/ami/multi_event_spec.rb +57 -0
- data/spec/ruby-asterisk/ami/parser_spec.rb +190 -0
- data/spec/ruby-asterisk/ami/reactor_spec.rb +290 -0
- data/spec/ruby-asterisk/ami_client_spec.rb +68 -0
- data/spec/ruby-asterisk/ari/client_spec.rb +168 -0
- data/spec/ruby-asterisk/ari/resources/bridge_spec.rb +42 -0
- data/spec/ruby-asterisk/ari/resources/channel_spec.rb +57 -0
- data/spec/ruby-asterisk/ari/resources/collection_spec.rb +66 -0
- data/spec/ruby-asterisk/ari/resources/endpoint_spec.rb +30 -0
- data/spec/ruby-asterisk/ari/resources/playback_spec.rb +33 -0
- data/spec/ruby-asterisk/ari/websocket_integration_spec.rb +86 -0
- data/spec/ruby-asterisk/ari/websocket_spec.rb +374 -0
- data/spec/ruby-asterisk/immutability_spec.rb +148 -0
- data/spec/ruby-asterisk/request_spec.rb +29 -9
- data/spec/ruby-asterisk/response_spec.rb +147 -148
- data/spec/spec_helper.rb +14 -0
- data/spec/support/mock_ami_server.rb +50 -0
- data/spec/support/mock_ari_websocket_server.rb +114 -0
- metadata +177 -21
- data/CHANGELOG +0 -3
- data/spec/ruby-asterisk/ruby_asterisk_spec.rb +0 -150
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ruby-asterisk/error'
|
|
4
|
+
require 'ruby-asterisk/ari/client'
|
|
5
|
+
|
|
6
|
+
RSpec.describe RubyAsterisk::ARI::Client do
|
|
7
|
+
let(:base_url) { 'http://localhost:8088' }
|
|
8
|
+
let(:api_key) { 'asterisk_api_key' }
|
|
9
|
+
let(:app_name) { 'stasis_app' }
|
|
10
|
+
|
|
11
|
+
let(:stubs) { Faraday::Adapter::Test::Stubs.new }
|
|
12
|
+
let(:test_conn) do
|
|
13
|
+
Faraday.new do |conn|
|
|
14
|
+
conn.adapter :test, stubs
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
subject(:client) { described_class.new(base_url, api_key, app_name) }
|
|
19
|
+
|
|
20
|
+
before do
|
|
21
|
+
client.instance_variable_set(:@connection, test_conn)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe '#initialize' do
|
|
25
|
+
it 'stores base_url' do
|
|
26
|
+
expect(client.base_url).to eq(base_url)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'stores app_name' do
|
|
30
|
+
expect(client.app_name).to eq(app_name)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe '#get' do
|
|
35
|
+
context 'when the request succeeds' do
|
|
36
|
+
before do
|
|
37
|
+
stubs.get('/ari/channels') do
|
|
38
|
+
[200, { 'Content-Type' => 'application/json' }, '[{"id":"12345"}]']
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'returns parsed JSON' do
|
|
43
|
+
result = client.get('/ari/channels')
|
|
44
|
+
expect(result).to eq([{ 'id' => '12345' }])
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
context 'when the server returns 404' do
|
|
49
|
+
before do
|
|
50
|
+
stubs.get('/ari/channels/missing') do
|
|
51
|
+
[404, { 'Content-Type' => 'application/json' }, '{"message":"Channel not found"}']
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'raises RubyAsterisk::Error' do
|
|
56
|
+
expect { client.get('/ari/channels/missing') }.to raise_error(RubyAsterisk::Error)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
context 'when the server returns 500' do
|
|
61
|
+
before do
|
|
62
|
+
stubs.get('/ari/channels') do
|
|
63
|
+
[500, { 'Content-Type' => 'application/json' }, '{"message":"Internal Server Error"}']
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'raises RubyAsterisk::Error' do
|
|
68
|
+
expect { client.get('/ari/channels') }.to raise_error(RubyAsterisk::Error)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe '#post' do
|
|
74
|
+
context 'when the request succeeds' do
|
|
75
|
+
before do
|
|
76
|
+
stubs.post('/ari/channels') do
|
|
77
|
+
[200, { 'Content-Type' => 'application/json' }, '{"id":"new_channel"}']
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it 'returns parsed JSON' do
|
|
82
|
+
result = client.post('/ari/channels', { endpoint: 'SIP/alice' })
|
|
83
|
+
expect(result).to eq({ 'id' => 'new_channel' })
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
context 'when the server returns 400' do
|
|
88
|
+
before do
|
|
89
|
+
stubs.post('/ari/channels') do
|
|
90
|
+
[400, { 'Content-Type' => 'application/json' }, '{"message":"Missing parameter"}']
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it 'raises RubyAsterisk::Error with the server message' do
|
|
95
|
+
expect { client.post('/ari/channels', {}) }.to raise_error(RubyAsterisk::Error, 'Missing parameter')
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
describe '#delete' do
|
|
101
|
+
context 'when the request succeeds' do
|
|
102
|
+
before do
|
|
103
|
+
stubs.delete('/ari/channels/12345') do
|
|
104
|
+
[204, {}, '']
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it 'returns nil for an empty body' do
|
|
109
|
+
result = client.delete('/ari/channels/12345')
|
|
110
|
+
expect(result).to be_nil
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
context 'when the server returns 404' do
|
|
115
|
+
before do
|
|
116
|
+
stubs.delete('/ari/channels/missing') do
|
|
117
|
+
[404, { 'Content-Type' => 'application/json' }, '{"message":"Channel not found"}']
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it 'raises RubyAsterisk::Error' do
|
|
122
|
+
expect { client.delete('/ari/channels/missing') }.to raise_error(RubyAsterisk::Error)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
describe '#channels' do
|
|
128
|
+
it 'returns a Collection for Channel resources' do
|
|
129
|
+
expect(client.channels).to be_a(RubyAsterisk::ARI::Resources::Collection)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
describe '#bridges' do
|
|
134
|
+
it 'returns a Collection for Bridge resources' do
|
|
135
|
+
expect(client.bridges).to be_a(RubyAsterisk::ARI::Resources::Collection)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
describe '#playbacks' do
|
|
140
|
+
it 'returns a Collection for Playback resources' do
|
|
141
|
+
expect(client.playbacks).to be_a(RubyAsterisk::ARI::Resources::Collection)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
describe '#endpoints' do
|
|
146
|
+
it 'returns a Collection for Endpoint resources' do
|
|
147
|
+
expect(client.endpoints).to be_a(RubyAsterisk::ARI::Resources::Collection)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
describe '#asterisk_info' do
|
|
152
|
+
let(:info_payload) do
|
|
153
|
+
{ 'build' => { 'os' => 'Linux' }, 'system' => { 'entity_id' => 'asterisk' } }.to_json
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
before do
|
|
157
|
+
stubs.get('/ari/asterisk/info') do
|
|
158
|
+
[200, { 'Content-Type' => 'application/json' }, info_payload]
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it 'performs GET /ari/asterisk/info and returns parsed data' do
|
|
163
|
+
result = client.asterisk_info
|
|
164
|
+
expect(result).to be_a(Hash)
|
|
165
|
+
expect(result).to have_key('build')
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ruby-asterisk/ari/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe RubyAsterisk::ARI::Resources::Bridge do
|
|
6
|
+
let(:client) { instance_double(RubyAsterisk::ARI::Client) }
|
|
7
|
+
let(:data) { { 'id' => 'bridge-456' } }
|
|
8
|
+
|
|
9
|
+
subject(:bridge) { described_class.new(data, client) }
|
|
10
|
+
|
|
11
|
+
describe '#id' do
|
|
12
|
+
it 'returns the bridge id from data' do
|
|
13
|
+
expect(bridge.id).to eq('bridge-456')
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe '#add_channel' do
|
|
18
|
+
it 'posts to the addChannel endpoint with the channel id' do
|
|
19
|
+
body = { channel: 'channel-123' }
|
|
20
|
+
allow(client).to receive(:post).with('/ari/bridges/bridge-456/addChannel', body).and_return(nil)
|
|
21
|
+
bridge.add_channel('channel-123')
|
|
22
|
+
expect(client).to have_received(:post).with('/ari/bridges/bridge-456/addChannel', body)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe '#remove_channel' do
|
|
27
|
+
it 'posts to the removeChannel endpoint with the channel id' do
|
|
28
|
+
body = { channel: 'channel-123' }
|
|
29
|
+
allow(client).to receive(:post).with('/ari/bridges/bridge-456/removeChannel', body).and_return(nil)
|
|
30
|
+
bridge.remove_channel('channel-123')
|
|
31
|
+
expect(client).to have_received(:post).with('/ari/bridges/bridge-456/removeChannel', body)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe '#destroy' do
|
|
36
|
+
it 'deletes the bridge' do
|
|
37
|
+
allow(client).to receive(:delete).with('/ari/bridges/bridge-456').and_return(nil)
|
|
38
|
+
bridge.destroy
|
|
39
|
+
expect(client).to have_received(:delete).with('/ari/bridges/bridge-456')
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ruby-asterisk/ari/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe RubyAsterisk::ARI::Resources::Channel do
|
|
6
|
+
let(:client) { instance_double(RubyAsterisk::ARI::Client) }
|
|
7
|
+
let(:data) { { 'id' => 'channel-123' } }
|
|
8
|
+
|
|
9
|
+
subject(:channel) { described_class.new(data, client) }
|
|
10
|
+
|
|
11
|
+
describe '#id' do
|
|
12
|
+
it 'returns the channel id from data' do
|
|
13
|
+
expect(channel.id).to eq('channel-123')
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe '#ring' do
|
|
18
|
+
it 'posts to the ring endpoint' do
|
|
19
|
+
allow(client).to receive(:post).with('/ari/channels/channel-123/ring').and_return(nil)
|
|
20
|
+
channel.ring
|
|
21
|
+
expect(client).to have_received(:post).with('/ari/channels/channel-123/ring')
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe '#answer' do
|
|
26
|
+
it 'posts to the answer endpoint' do
|
|
27
|
+
allow(client).to receive(:post).with('/ari/channels/channel-123/answer').and_return(nil)
|
|
28
|
+
channel.answer
|
|
29
|
+
expect(client).to have_received(:post).with('/ari/channels/channel-123/answer')
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe '#hangup' do
|
|
34
|
+
it 'deletes the channel' do
|
|
35
|
+
allow(client).to receive(:delete).with('/ari/channels/channel-123').and_return(nil)
|
|
36
|
+
channel.hangup
|
|
37
|
+
expect(client).to have_received(:delete).with('/ari/channels/channel-123')
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe '#play' do
|
|
42
|
+
let(:expected_body) { { media: 'sound:hello-world' } }
|
|
43
|
+
|
|
44
|
+
it 'posts to the play endpoint with the media parameter' do
|
|
45
|
+
allow(client).to receive(:post).with('/ari/channels/channel-123/play', expected_body).and_return({})
|
|
46
|
+
channel.play('sound:hello-world')
|
|
47
|
+
expect(client).to have_received(:post).with('/ari/channels/channel-123/play', expected_body)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'merges additional params into the request body' do
|
|
51
|
+
body = { media: 'sound:hello-world', lang: 'en' }
|
|
52
|
+
allow(client).to receive(:post).with('/ari/channels/channel-123/play', body).and_return({})
|
|
53
|
+
channel.play('sound:hello-world', { lang: 'en' })
|
|
54
|
+
expect(client).to have_received(:post).with('/ari/channels/channel-123/play', body)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ruby-asterisk/ari/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe RubyAsterisk::ARI::Resources::Collection do
|
|
6
|
+
let(:client) { instance_double(RubyAsterisk::ARI::Client) }
|
|
7
|
+
let(:resource_class) { RubyAsterisk::ARI::Resources::Channel }
|
|
8
|
+
|
|
9
|
+
subject(:collection) { described_class.new(resource_class, '/ari/channels', client) }
|
|
10
|
+
|
|
11
|
+
describe '#get' do
|
|
12
|
+
let(:channel_data) { { 'id' => 'channel-123' } }
|
|
13
|
+
|
|
14
|
+
before do
|
|
15
|
+
allow(client).to receive(:get).with('/ari/channels/channel-123').and_return(channel_data)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'fetches the resource by id' do
|
|
19
|
+
collection.get('channel-123')
|
|
20
|
+
expect(client).to have_received(:get).with('/ari/channels/channel-123')
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'returns the resource wrapped in the correct class' do
|
|
24
|
+
result = collection.get('channel-123')
|
|
25
|
+
expect(result).to be_a(RubyAsterisk::ARI::Resources::Channel)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'passes the client to the resource' do
|
|
29
|
+
result = collection.get('channel-123')
|
|
30
|
+
expect(result.client).to eq(client)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'passes the data to the resource' do
|
|
34
|
+
result = collection.get('channel-123')
|
|
35
|
+
expect(result.data).to eq(channel_data)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe '#list' do
|
|
40
|
+
let(:channels_data) { [{ 'id' => 'ch-1' }, { 'id' => 'ch-2' }] }
|
|
41
|
+
|
|
42
|
+
before do
|
|
43
|
+
allow(client).to receive(:get).with('/ari/channels', {}).and_return(channels_data)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'fetches the full list of resources' do
|
|
47
|
+
collection.list
|
|
48
|
+
expect(client).to have_received(:get).with('/ari/channels', {})
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'returns an array of wrapped resources' do
|
|
52
|
+
result = collection.list
|
|
53
|
+
expect(result).to all(be_a(RubyAsterisk::ARI::Resources::Channel))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'returns the correct number of resources' do
|
|
57
|
+
expect(collection.list.size).to eq(2)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'accepts query params' do
|
|
61
|
+
allow(client).to receive(:get).with('/ari/channels', { state: 'Up' }).and_return(channels_data)
|
|
62
|
+
collection.list({ state: 'Up' })
|
|
63
|
+
expect(client).to have_received(:get).with('/ari/channels', { state: 'Up' })
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ruby-asterisk/ari/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe RubyAsterisk::ARI::Resources::Endpoint do
|
|
6
|
+
let(:client) { instance_double(RubyAsterisk::ARI::Client) }
|
|
7
|
+
let(:data) do
|
|
8
|
+
{ 'technology' => 'SIP', 'resource' => 'alice', 'state' => 'online' }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
subject(:endpoint) { described_class.new(data, client) }
|
|
12
|
+
|
|
13
|
+
describe '#technology' do
|
|
14
|
+
it 'returns the technology from data' do
|
|
15
|
+
expect(endpoint.technology).to eq('SIP')
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe '#resource' do
|
|
20
|
+
it 'returns the resource from data' do
|
|
21
|
+
expect(endpoint.resource).to eq('alice')
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe '#state' do
|
|
26
|
+
it 'returns the state from data' do
|
|
27
|
+
expect(endpoint.state).to eq('online')
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ruby-asterisk/ari/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe RubyAsterisk::ARI::Resources::Playback do
|
|
6
|
+
let(:client) { instance_double(RubyAsterisk::ARI::Client) }
|
|
7
|
+
let(:data) { { 'id' => 'playback-789' } }
|
|
8
|
+
|
|
9
|
+
subject(:playback) { described_class.new(data, client) }
|
|
10
|
+
|
|
11
|
+
describe '#id' do
|
|
12
|
+
it 'returns the playback id from data' do
|
|
13
|
+
expect(playback.id).to eq('playback-789')
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe '#stop' do
|
|
18
|
+
it 'deletes the playback' do
|
|
19
|
+
allow(client).to receive(:delete).with('/ari/playbacks/playback-789').and_return(nil)
|
|
20
|
+
playback.stop
|
|
21
|
+
expect(client).to have_received(:delete).with('/ari/playbacks/playback-789')
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe '#control' do
|
|
26
|
+
it 'posts the operation to the control endpoint' do
|
|
27
|
+
body = { operation: 'pause' }
|
|
28
|
+
allow(client).to receive(:post).with('/ari/playbacks/playback-789/control', body).and_return(nil)
|
|
29
|
+
playback.control('pause')
|
|
30
|
+
expect(client).to have_received(:post).with('/ari/playbacks/playback-789/control', body)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'support/mock_ari_websocket_server'
|
|
5
|
+
|
|
6
|
+
RSpec.describe RubyAsterisk::ARI::WebSocket, 'integration' do
|
|
7
|
+
let(:server) { MockAriWebSocketServer.new }
|
|
8
|
+
let(:base_url) { "http://127.0.0.1:#{server.port}" }
|
|
9
|
+
let(:logger) { Logger.new(File::NULL) }
|
|
10
|
+
|
|
11
|
+
after do
|
|
12
|
+
@websocket&.disconnect
|
|
13
|
+
server.stop
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def connect(options = {})
|
|
17
|
+
opened = Queue.new
|
|
18
|
+
defaults = { logger: logger, reconnect_delay: 0.1 }
|
|
19
|
+
@websocket = described_class.new(base_url, 'api_key', 'test_app', defaults.merge(options))
|
|
20
|
+
yield @websocket if block_given?
|
|
21
|
+
@websocket.connect { opened << true }
|
|
22
|
+
MockAriWebSocketServer.pop(opened)
|
|
23
|
+
@websocket
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'completes the handshake and fires the on_connect callback' do
|
|
27
|
+
websocket = connect
|
|
28
|
+
|
|
29
|
+
expect(websocket.connected?).to be(true)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'dispatches server events to registered callbacks' do
|
|
33
|
+
received = Queue.new
|
|
34
|
+
connect { |ws| ws.on('StasisStart') { |data| received << data } }
|
|
35
|
+
|
|
36
|
+
server.broadcast('type' => 'StasisStart', 'channel' => { 'id' => '42' })
|
|
37
|
+
|
|
38
|
+
event = MockAriWebSocketServer.pop(received)
|
|
39
|
+
expect(event).to eq('type' => 'StasisStart', 'channel' => { 'id' => '42' })
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'sends messages to the server' do
|
|
43
|
+
websocket = connect
|
|
44
|
+
|
|
45
|
+
expect(websocket.send_message?({ 'type' => 'ping' })).to be(true)
|
|
46
|
+
expect(MockAriWebSocketServer.pop(server.messages)).to eq('{"type":"ping"}')
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'sends pings at the configured interval' do
|
|
50
|
+
connect(ping_interval: 0.1)
|
|
51
|
+
|
|
52
|
+
expect(MockAriWebSocketServer.pop(server.pings)).not_to be_nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'reconnects automatically after a connection drop' do
|
|
56
|
+
received = Queue.new
|
|
57
|
+
connect { |ws| ws.on('StasisStart') { |data| received << data } }
|
|
58
|
+
MockAriWebSocketServer.pop(server.connections) # first handshake
|
|
59
|
+
|
|
60
|
+
server.drop_clients
|
|
61
|
+
MockAriWebSocketServer.pop(server.connections) # second handshake
|
|
62
|
+
|
|
63
|
+
server.broadcast('type' => 'StasisStart')
|
|
64
|
+
expect(MockAriWebSocketServer.pop(received)).to eq('type' => 'StasisStart')
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'does not reconnect when auto_reconnect is disabled' do
|
|
68
|
+
websocket = connect(auto_reconnect: false)
|
|
69
|
+
thread = websocket.instance_variable_get(:@connection_thread)
|
|
70
|
+
|
|
71
|
+
server.drop_clients
|
|
72
|
+
|
|
73
|
+
expect(thread.join(2)).to eq(thread)
|
|
74
|
+
expect(websocket.connected?).to be(false)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'stops cleanly on disconnect' do
|
|
78
|
+
websocket = connect
|
|
79
|
+
thread = websocket.instance_variable_get(:@connection_thread)
|
|
80
|
+
|
|
81
|
+
websocket.disconnect
|
|
82
|
+
|
|
83
|
+
expect(websocket.connected?).to be(false)
|
|
84
|
+
expect(thread.alive?).to be(false)
|
|
85
|
+
end
|
|
86
|
+
end
|