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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c06bf91d26d756f01fff17c5740c7ea254ce49474b3be83ea8d57dd3e6ed472
4
- data.tar.gz: 0125a4facf8ed3d4bdc592e11c1c0bb376e43f451f985c13ec90e01de9a9047e
3
+ metadata.gz: a7531d7fa92db501d0d81a5b9d8ad8108614c63d1260b465a2a0367ef716293c
4
+ data.tar.gz: 496ac9be22cf2840ae6eeaa5897347ad0402039daad423030b446aae17a8c23e
5
5
  SHA512:
6
- metadata.gz: 62883cef19ef0ee4520ddbcfb62fcf0a64186836f3e8389242007e4c76f8ddfbd70d848832788358524ed19f4d5e74dfb8aa468b7372dd80631052ff0b63eede
7
- data.tar.gz: 2db626b561d48b858ba37d8703d3c03f11e2e3b6886a8ccfbce728caa5bf6f2f5a503fc418949a3990a0a5be58bda125507a17d61de8cc05203a85d10bbfb0fe
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
- Jbr.mock.phone_error = { code: '21452' }
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
- Jbr.mock.phone = { id: 'SM083e290bef7794c407f14e65a891aa6d', number: '8009005000' }
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 messaging_service_sid: Twi.lio.messaging_sid.to_s,
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
@@ -7,6 +7,8 @@ module Twi
7
7
  def sid = @params[:id]
8
8
 
9
9
  def create
10
+ Twi.mock.messages << @params.slice(:sender, :recipient, :content, :media_url)
11
+
10
12
  if error = Twi.mock.message_error
11
13
  Twi.mock.message_error = nil
12
14
  raise Error, error
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
@@ -19,6 +19,10 @@ module Twi
19
19
  message(...).tap &:create
20
20
  end
21
21
 
22
+ def create_call(...)
23
+ (@mock ? Mock::Call : Call).new(...).tap &:create
24
+ end
25
+
22
26
  def conversation(...)
23
27
  (@mock ? Mock::Conversation : Conversation).new(...)
24
28
  end
data/lib/twi/version.rb CHANGED
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Twi
4
- VERSION = '0.7.0'
2
+ VERSION = '0.9.0'
5
3
  end
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.7.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
- description: Enhances OpenSSL::HMAC with timestamp.
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: API Signature with timestamped MAC (Message Authentication Code).
157
+ summary: An object-oriented Ruby client for the Twilio API.
98
158
  test_files: []