twi 0.7.0 → 0.9.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 +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +66 -2
- data/lib/twi/call.rb +36 -0
- data/lib/twi/message.rb +12 -2
- data/lib/twi/mock/call.rb +25 -0
- data/lib/twi/mock/message.rb +2 -0
- data/lib/twi/mock.rb +7 -0
- data/lib/twi/mocking.rb +4 -0
- data/lib/twi/version.rb +1 -3
- data/lib/twi.rb +2 -2
- metadata +63 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a7531d7fa92db501d0d81a5b9d8ad8108614c63d1260b465a2a0367ef716293c
|
|
4
|
+
data.tar.gz: 496ac9be22cf2840ae6eeaa5897347ad0402039daad423030b446aae17a8c23e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e2d906451f3d4c5e8fc2f1cb8fd26b998998bee953d89675594b822b2cad6f55b06b80770540a135d0381d0f537cd16c0f5faada49e0ead42c23cf54302a5460
|
|
7
|
+
data.tar.gz: c1605aa3aca9c09af360e1ee871c3a9e29787498ad85ba3c38547bda3568f9237d9c6c7c4d45919c7875eb0b08bca0140677aa2145f654cb1e559fb6de34b23b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
## [0.9.0] - 2026-09-14
|
|
4
|
+
|
|
5
|
+
- [New] Place a call with Twi.create_call, telling Twilio where to read its TwiML
|
|
6
|
+
- [New] Add Twi::Call, to read the webhook Twilio reports a call on
|
|
7
|
+
- [New] Add Twi::Call.params_for, to build a call webhook payload in a test
|
|
8
|
+
- [New] Add Twi.mock.call, Twi.mock.call_error and Twi.mock.calls
|
|
9
|
+
|
|
10
|
+
## [0.8.0] - 2026-09-11
|
|
11
|
+
|
|
12
|
+
- [New] Send media with a message: `Twi.create_message media_url:` carries it as an MMS
|
|
13
|
+
- [New] Record what was sent on Twi.mock.messages, so a suite can assert it
|
|
14
|
+
|
|
1
15
|
## [0.7.0] - 2026-08-19
|
|
2
16
|
|
|
3
17
|
- [New] Add Twi.reset_mock, to stop a mocked answer outliving the test that arranged it
|
data/README.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# The enhanced Twilio API Ruby client
|
|
2
2
|
|
|
3
|
+
## How to install
|
|
4
|
+
|
|
5
|
+
To install on your system, run:
|
|
6
|
+
|
|
7
|
+
gem install twi
|
|
8
|
+
|
|
9
|
+
To use inside a bundled Ruby project, add this line to the Gemfile:
|
|
10
|
+
|
|
11
|
+
gem 'twi', '~> 0.9.0'
|
|
12
|
+
|
|
13
|
+
This gem follows [Semantic Versioning](https://semver.org). Below 1.0 a minor release may
|
|
14
|
+
break an API, so the pin stops short of 0.10: `bundle update` never crosses one.
|
|
15
|
+
|
|
3
16
|
## Available methods
|
|
4
17
|
|
|
5
18
|
### Twi::Message
|
|
@@ -43,6 +56,36 @@ Twi::Delivery.params_for id: 'SM12', status: 'sent'
|
|
|
43
56
|
# => { SmsSid: 'SM12', MessgeStatus: 'sent', ErrorCode: nil }
|
|
44
57
|
```
|
|
45
58
|
|
|
59
|
+
### Twi::Call
|
|
60
|
+
|
|
61
|
+
To place an outbound call, telling Twilio where to read its instructions and where to
|
|
62
|
+
report on how it went:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
call = Twi.create_call sender: '8008008000', recipient: '8009007000',
|
|
66
|
+
url: 'https://example.com/twiml', status_callback: 'https://example.com/calls'
|
|
67
|
+
call.id # => 'CA083e290bef7794c407f14e22a891aa6d'
|
|
68
|
+
call.status # => 'queued'
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
When receiving an update about a call via webhook:
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
call = Twi::Call.new params
|
|
75
|
+
call.id # => 'CA083e290bef7794c407f14e22a891aa6d'
|
|
76
|
+
call.status # => 'completed'
|
|
77
|
+
call.digits # => '2'
|
|
78
|
+
call.sender # => '8008008000'
|
|
79
|
+
call.recipient # => '8009007000'
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
When building a Twilio-like webhook payload:
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
Twi::Call.params_for id: 'CA12', status: 'completed', digits: '2'
|
|
86
|
+
# => { CallSid: 'CA12', CallStatus: 'completed', Digits: '2' }
|
|
87
|
+
```
|
|
88
|
+
|
|
46
89
|
### Twi::Event
|
|
47
90
|
|
|
48
91
|
When receiving events about a conversation:
|
|
@@ -74,13 +117,34 @@ Use these methods to mock request to Twilio when testing an app:
|
|
|
74
117
|
Mock an error when creating an incoming phone number:
|
|
75
118
|
|
|
76
119
|
```ruby
|
|
77
|
-
|
|
120
|
+
Twi.mock.phone_error = { code: '21452' }
|
|
78
121
|
```
|
|
79
122
|
|
|
80
123
|
Mock successfully creating an incoming phone number:
|
|
81
124
|
|
|
82
125
|
```ruby
|
|
83
|
-
|
|
126
|
+
Twi.mock.phone = { id: 'SM083e290bef7794c407f14e65a891aa6d', number: '8009005000' }
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Calls
|
|
130
|
+
|
|
131
|
+
Mock an error when placing a call:
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
Twi.mock.call_error = { code: '21215' }
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Mock successfully placing a call:
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
Twi.mock.call = { id: 'CA083e290bef7794c407f14e22a891aa6d', status: 'queued' }
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Read back every call placed since the mock was reset, to assert what a suite sent:
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
Twi.mock.calls # => [{ sender: '8008008000', recipient: '8009007000', url: 'https://example.com/twiml',
|
|
147
|
+
# status_callback: 'https://example.com/calls' }]
|
|
84
148
|
```
|
|
85
149
|
|
|
86
150
|
|
data/lib/twi/call.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Enhances the Twilio Ruby gem with an object-oriented approach.
|
|
2
|
+
module Twi
|
|
3
|
+
# The representation of an outbound phone call.
|
|
4
|
+
class Call < Resource
|
|
5
|
+
# @return [String] unique identifier
|
|
6
|
+
def id = @params['CallSid']
|
|
7
|
+
|
|
8
|
+
# @return [String] call status, one of queued, ringing, in-progress, completed, busy,
|
|
9
|
+
# no-answer, canceled, failed # TODO: make an enum
|
|
10
|
+
def status = @params['CallStatus']
|
|
11
|
+
|
|
12
|
+
# @return [String, nil] digits the callee pressed in answer to a Gather.
|
|
13
|
+
def digits = @params['Digits']
|
|
14
|
+
|
|
15
|
+
# @return [String] 10-digit phone number placing the call.
|
|
16
|
+
def sender = remove_prefix_from @params['From']
|
|
17
|
+
|
|
18
|
+
# @return [String] 10-digit phone number being called.
|
|
19
|
+
def recipient = remove_prefix_from @params['To']
|
|
20
|
+
|
|
21
|
+
# Places the call, asking Twilio to fetch its instructions from +url+.
|
|
22
|
+
def create
|
|
23
|
+
call = api_client.calls.create from: "+1#{@params[:sender]}", to: "+1#{@params[:recipient]}",
|
|
24
|
+
url: @params[:url], method: 'GET', status_callback: @params[:status_callback]
|
|
25
|
+
|
|
26
|
+
@params = { 'CallSid' => call.sid, 'CallStatus' => call.status }
|
|
27
|
+
rescue Twilio::REST::RestError => error
|
|
28
|
+
raise Error, code: error.code, message: error.error_message
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @return [Hash] the shape of the payload send by Twilio to the callback URL.
|
|
32
|
+
def self.params_for(id:, status:, digits: nil)
|
|
33
|
+
{ CallSid: id, CallStatus: status, Digits: digits }.compact
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/twi/message.rb
CHANGED
|
@@ -40,8 +40,7 @@ module Twi
|
|
|
40
40
|
|
|
41
41
|
# Sends a message.
|
|
42
42
|
def create
|
|
43
|
-
message = api_client.messages.create
|
|
44
|
-
from: "+1#{@params[:sender]}", to: "+1#{@params[:recipient]}", body: @params[:content]
|
|
43
|
+
message = api_client.messages.create **create_params
|
|
45
44
|
|
|
46
45
|
@params = { 'MessageSid' => message.sid }
|
|
47
46
|
@id = message.sid
|
|
@@ -62,6 +61,17 @@ module Twi
|
|
|
62
61
|
|
|
63
62
|
private
|
|
64
63
|
|
|
64
|
+
# Twilio fetches whatever `media_url` names and carries it as an MMS, so a vCard among
|
|
65
|
+
# them arrives as a contact card the reader can save rather than a link they must open.
|
|
66
|
+
# @return [Hash] the arguments Twilio takes for one outbound message.
|
|
67
|
+
def create_params
|
|
68
|
+
media = Array @params[:media_url]
|
|
69
|
+
|
|
70
|
+
{ messaging_service_sid: Twi.lio.messaging_sid.to_s, from: "+1#{@params[:sender]}",
|
|
71
|
+
to: "+1#{@params[:recipient]}", body: @params[:content] }
|
|
72
|
+
.merge(media.any? ? { media_url: media } : {})
|
|
73
|
+
end
|
|
74
|
+
|
|
65
75
|
def self.media_params_for(media = [])
|
|
66
76
|
media.each_with_index.inject({ NumMedia: media.size.to_s }) do |hash, (item, index)|
|
|
67
77
|
url_key = "MediaUrl#{index}".to_sym
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Twi
|
|
2
|
+
class Mock::Call < Call
|
|
3
|
+
# @return [String] unique identifier
|
|
4
|
+
def id = @params[:id]
|
|
5
|
+
|
|
6
|
+
# @return [String] call status
|
|
7
|
+
def status = @params[:status]
|
|
8
|
+
|
|
9
|
+
# Records the call rather than placing it, and answers whatever the suite arranged.
|
|
10
|
+
def create
|
|
11
|
+
Twi.mock.calls << @params.slice(:sender, :recipient, :url, :status_callback)
|
|
12
|
+
|
|
13
|
+
if error = Twi.mock.call_error
|
|
14
|
+
Twi.mock.call_error = nil
|
|
15
|
+
raise Error, error
|
|
16
|
+
elsif Twi.mock.call
|
|
17
|
+
@params[:id] = Twi.mock.call[:id]
|
|
18
|
+
@params[:status] = Twi.mock.call[:status]
|
|
19
|
+
else
|
|
20
|
+
@params[:id] = "CA#{rand}"
|
|
21
|
+
@params[:status] = 'queued'
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/twi/mock/message.rb
CHANGED
data/lib/twi/mock.rb
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
module Twi
|
|
2
2
|
class Mock
|
|
3
3
|
attr_accessor :phone, :phone_error
|
|
4
|
+
attr_accessor :call, :call_error
|
|
4
5
|
attr_accessor :message, :message_error
|
|
5
6
|
attr_accessor :conversation, :conversation_error
|
|
6
7
|
attr_accessor :medium
|
|
8
|
+
|
|
9
|
+
# @return [Array<Hash>] every message sent since the mock was reset, in order.
|
|
10
|
+
def messages = @messages ||= []
|
|
11
|
+
|
|
12
|
+
# @return [Array<Hash>] every call placed since the mock was reset, in order.
|
|
13
|
+
def calls = @calls ||= []
|
|
7
14
|
end
|
|
8
15
|
end
|
data/lib/twi/mocking.rb
CHANGED
data/lib/twi/version.rb
CHANGED
data/lib/twi.rb
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
1
|
require 'action_controller'
|
|
4
2
|
require 'action_controller/metal/strong_parameters'
|
|
5
3
|
require 'active_support/core_ext/enumerable'
|
|
@@ -14,6 +12,7 @@ require 'twi/config'
|
|
|
14
12
|
require 'twi/resource'
|
|
15
13
|
require 'twi/message'
|
|
16
14
|
require 'twi/delivery'
|
|
15
|
+
require 'twi/call'
|
|
17
16
|
require 'twi/participant'
|
|
18
17
|
require 'twi/medium'
|
|
19
18
|
require 'twi/event'
|
|
@@ -30,4 +29,5 @@ require 'twi/mock/conversation'
|
|
|
30
29
|
require 'twi/mock/event'
|
|
31
30
|
require 'twi/mock/medium'
|
|
32
31
|
require 'twi/mock/message'
|
|
32
|
+
require 'twi/mock/call'
|
|
33
33
|
require 'twi/mocking'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: twi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Claudio Baccigalupo
|
|
@@ -37,7 +37,64 @@ dependencies:
|
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '0'
|
|
40
|
-
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: minitest
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rake
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: simplecov
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: webmock
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
96
|
+
description: Sends messages, places calls and reads the webhooks Twilio answers them
|
|
97
|
+
with.
|
|
41
98
|
email:
|
|
42
99
|
- claudiob@users.noreply.github.com
|
|
43
100
|
executables: []
|
|
@@ -48,6 +105,7 @@ files:
|
|
|
48
105
|
- LICENSE.txt
|
|
49
106
|
- README.md
|
|
50
107
|
- lib/twi.rb
|
|
108
|
+
- lib/twi/call.rb
|
|
51
109
|
- lib/twi/config.rb
|
|
52
110
|
- lib/twi/conversation.rb
|
|
53
111
|
- lib/twi/conversation_event.rb
|
|
@@ -60,6 +118,7 @@ files:
|
|
|
60
118
|
- lib/twi/message.rb
|
|
61
119
|
- lib/twi/message_event.rb
|
|
62
120
|
- lib/twi/mock.rb
|
|
121
|
+
- lib/twi/mock/call.rb
|
|
63
122
|
- lib/twi/mock/conversation.rb
|
|
64
123
|
- lib/twi/mock/event.rb
|
|
65
124
|
- lib/twi/mock/medium.rb
|
|
@@ -78,6 +137,7 @@ metadata:
|
|
|
78
137
|
homepage_uri: https://github.com/claudiob/twi
|
|
79
138
|
source_code_uri: https://github.com/claudiob/twi
|
|
80
139
|
changelog_uri: https://github.com/claudiob/twi
|
|
140
|
+
documentation_uri: https://rubydoc.info/gems/twi
|
|
81
141
|
rdoc_options: []
|
|
82
142
|
require_paths:
|
|
83
143
|
- lib
|
|
@@ -94,5 +154,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
94
154
|
requirements: []
|
|
95
155
|
rubygems_version: 4.0.3
|
|
96
156
|
specification_version: 4
|
|
97
|
-
summary:
|
|
157
|
+
summary: An object-oriented Ruby client for the Twilio API.
|
|
98
158
|
test_files: []
|