essential 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.
@@ -0,0 +1,21 @@
1
+ module Essential
2
+ class Resource
3
+ module Create
4
+
5
+ def create(params, headers: @headers)
6
+ params = (params || {}).clone
7
+ params = @params.merge(params) if @params
8
+ params = filter_attrs(params)
9
+ response = request(:post,
10
+ url: url,
11
+ params: params,
12
+ headers: headers)
13
+ json = JSON.parse(response)
14
+ json = @params.merge(json) if @params
15
+ json = @attrs.merge(json) if @attrs
16
+ from_attributes(json)
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ module Essential
2
+ class Resource
3
+ module Delete
4
+
5
+ def delete(headers: @headers)
6
+ response = self.class.request(:delete, url: self.url, headers: headers)
7
+
8
+ if response.nil? || response.empty?
9
+ nil
10
+ else
11
+ json = JSON.parse(response)
12
+ json = @params.merge(json) if @params
13
+ json = @attrs.merge(json) if @attrs
14
+ init_from(json)
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ module Essential
2
+ class Resource
3
+ module List
4
+
5
+ def retrieve(sid, fetch: true, headers: @headers)
6
+ me = self.new(sid: sid, headers: headers)
7
+ me.fetch() if fetch
8
+ me
9
+ end
10
+
11
+ def list(params: {}, url: nil, headers: @headers)
12
+ return PaginatorProxy.new(self, url: url, params: params, headers: headers)
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,115 @@
1
+ module Essential
2
+ class Resource
3
+ class PaginatorProxy
4
+ include Create
5
+ include Enumerable
6
+
7
+ attr_reader :proxied_class, :url
8
+ attr_reader :headers, :params
9
+ attr_reader :total, :pages, :per_page
10
+ alias :size :total
11
+ alias :count :total
12
+
13
+ def initialize(proxied_class, url: nil, params: {}, headers: {}, attrs: {})
14
+ @fetched_pages = {}
15
+ @proxied_class = proxied_class
16
+ @url = url || @proxied_class.url
17
+ @params = params || {}
18
+ @headers = headers || {}
19
+ @attrs = attrs || {}
20
+
21
+ @total = 0
22
+ @pages = 0
23
+ @per_page = 0
24
+
25
+ self.refresh
26
+ end
27
+
28
+ def [](i)
29
+ i = i.to_i
30
+
31
+ # support reverse wrap
32
+ i += @total if i < 0
33
+
34
+ return nil if i >= @total || i < 0
35
+
36
+ page = (i / @per_page) + 1
37
+ index = i % @per_page
38
+
39
+ fetch_page(page) unless @fetched_pages.key?(page)
40
+ @fetched_pages[page][index]
41
+ end
42
+
43
+ def last
44
+ self[-1]
45
+ end
46
+
47
+ def each
48
+ return enum_for(:each) unless block_given?
49
+
50
+ (0...self.total).each do |idx|
51
+ yield self[idx]
52
+ end
53
+ end
54
+
55
+ def refresh
56
+ @fetched_pages.clear
57
+ fetch_page(nil)
58
+ end
59
+
60
+ def as_json
61
+ self.map(&:as_json)
62
+ end
63
+
64
+ def inspect
65
+ format(
66
+ '#<%s:0x%s %s>',
67
+ self.class.name,
68
+ (self.object_id << 1).to_s(16),
69
+ [:total, :per_page, :headers, :params].map do |m|
70
+ attr = self.send(m)
71
+ if attr
72
+ format('@%s=%s', m, attr.inspect)
73
+ else
74
+ nil
75
+ end
76
+ end.compact.join(', ')
77
+ )
78
+ end
79
+
80
+ protected
81
+
82
+ def fetch_page(page)
83
+ params = (@params || {}).clone
84
+ params = {page: page}.merge(params) if page
85
+
86
+ # Actually touch the network
87
+ response = @proxied_class.request(
88
+ :get,
89
+ url: @url,
90
+ params: params,
91
+ headers: @headers
92
+ )
93
+
94
+ # update our paging information
95
+ @total = response.headers[:total].to_i
96
+ @per_page = response.headers[:per_page].to_i
97
+ @pages = (@total.to_f / @per_page).ceil
98
+
99
+ json = JSON.parse(response)
100
+ @fetched_pages[page || 1] = json.map do |json|
101
+ # persist @attrs across returned objects, as when listing a Subscriber Property.
102
+ attrs = @attrs.merge(json)
103
+ @proxied_class.from_attributes(attrs, headers: @headers)
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ def method_missing(method, *args, &block)
110
+ @proxied_class.send(method, *args, &block)
111
+ end
112
+
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,26 @@
1
+ module Essential
2
+ class Resource
3
+ module Update
4
+
5
+ def update(params, headers: @headers)
6
+ params = filter_attrs(params || {})
7
+ params = @params.merge(params) if @params
8
+ params = self.as_json.merge(params)
9
+ params = filter_attrs(params)
10
+
11
+ response = self.class.request(
12
+ :put,
13
+ url: self.url,
14
+ params: params,
15
+ headers: headers
16
+ )
17
+ json = JSON.parse(response)
18
+ json = @params.merge(json) if @params
19
+ json = @attrs.merge(json) if @attrs
20
+ init_from(json)
21
+ self
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ module Essential
2
+ VERSION = '0.9.0'.freeze
3
+ end
@@ -0,0 +1,22 @@
1
+ require 'test_helper'
2
+
3
+ class AccountTest < EssentialTest
4
+
5
+ def test_invalid_credentials
6
+ error = assert_raises Essential::APIError do
7
+ Essential::Account.retrieve(headers: {sid: 'sid_nope'})
8
+ end
9
+ assert_equal 401, error.http_status
10
+ end
11
+
12
+ def test_valid_credentials
13
+ account = Essential::Account.retrieve
14
+
15
+ prefixed_sid = Essential.sid
16
+ prefixed_sid = 'ac_' << prefixed_sid unless 0 == prefixed_sid.index('ac_')
17
+
18
+ assert_equal prefixed_sid, account.sid
19
+ assert_equal Essential.token, account.token
20
+ refute_nil account.name
21
+ end
22
+ end
@@ -0,0 +1,62 @@
1
+ require 'test_helper'
2
+
3
+ class ChannelTest < EssentialTest
4
+ include Essential::Messaging
5
+
6
+ def test_retrieve
7
+ assert_raises ArgumentError do
8
+ channel = Channel.retrieve('')
9
+ end
10
+
11
+ assert_raises ArgumentError do
12
+ Channel.retrieve(nil)
13
+ end
14
+
15
+ channel = Channel.retrieve('ch_p03Gjl8Uzn0RkZSpHXHnrw')
16
+ assert_equal 'ch_p03Gjl8Uzn0RkZSpHXHnrw', channel.sid
17
+ refute_nil channel.name
18
+ refute_nil channel.created_at
19
+ refute_nil channel.updated_at
20
+ end
21
+
22
+ def test_list
23
+ channels = Channel.list
24
+
25
+ assert channels.size > 1, 'must have more than one channel'
26
+
27
+ first = channels[0]
28
+ last = channels[-1]
29
+
30
+ refute_equal first.sid, last.sid
31
+ end
32
+
33
+ def test_update
34
+ secondary = Channel.list.find do |ch|
35
+ ch.name != 'default' && ch.name != ch.name.reverse
36
+ end
37
+
38
+ refute_nil secondary
39
+ sid = secondary.sid
40
+ original_name = secondary.name
41
+
42
+ refute_equal 'default', secondary.name
43
+
44
+ secondary.update(name: secondary.name.reverse)
45
+ assert_equal sid, secondary.sid
46
+ refute_equal original_name, secondary.name
47
+ assert_equal original_name, secondary.name.reverse
48
+ end
49
+
50
+ def test_relations
51
+ channel = Channel.retrieve('p03Gjl8Uzn0RkZSpHXHnrw')
52
+ assert_equal Essential::Account.retrieve, channel.account
53
+
54
+ subs = channel.subscribers
55
+ refute_equal 0, subs.count, 'test requires at least one Subscriber'
56
+ assert subs.all? {|s| Subscriber === s}
57
+
58
+ mess = channel.messages
59
+ refute_equal 0, mess.count, 'test requires at least one Message'
60
+ assert mess.all? {|m| Message === m}
61
+ end
62
+ end
@@ -0,0 +1,119 @@
1
+ require 'test_helper'
2
+
3
+ class MessageTest < EssentialTest
4
+ include Essential::Messaging
5
+
6
+ def setup
7
+ @channel = Channel.list.first
8
+ assert_equal 'default', @channel.name, 'default channel must exist'
9
+ @subscriber = @channel.subscribers.first
10
+ @transport = Transport.retrieve(@subscriber.transport_sid)
11
+ end
12
+
13
+ def test_create
14
+ required = [
15
+ {body: 'a body', subscriber: @subscriber.sid},
16
+ {body: 'a body', subscriber: @subscriber.phone_number}
17
+ ]
18
+
19
+ optional = [
20
+ {},
21
+ {transport: @transport.sid},
22
+ {transport: @transport.endpoint},
23
+ {channel: @channel.sid},
24
+ {channel: @channel.name},
25
+ ]
26
+
27
+ required.each do |req|
28
+ optional.each do |opt|
29
+ assert_valid_message Message.create(req.merge(opt))
30
+ end
31
+ end
32
+
33
+ end
34
+
35
+ def test_create_errors
36
+ map_of_brokenness = {
37
+ nil => [:body, :subscriber],
38
+
39
+ {} => [:body, :subscriber],
40
+
41
+ { body: 'haha'} => [:subscriber],
42
+
43
+ { body: 'haha', subscriber: 'not_a_sid'} => [:subscriber],
44
+
45
+ { body: 'haha',
46
+ subscriber: 'not_a_phone_number' } => [:subscriber],
47
+
48
+ { body: 'haha',
49
+ subscriber: @subscriber.sid,
50
+ transport: 'not_a_sid' } => [:transport],
51
+
52
+ { body: 'haha',
53
+ subscriber: @subscriber.sid,
54
+ transport: 'not_a_transport' } => [:transport],
55
+
56
+ { body: 'haha',
57
+ subscriber: @subscriber.sid,
58
+ channel: 'not_a_sid' } => [:channel],
59
+
60
+ { body: 'haha',
61
+ subscriber_sid: @subscriber.sid,
62
+ channel: 'olives' } => [:channel],
63
+ }
64
+
65
+ map_of_brokenness.each do |params, errors|
66
+ assert_error_with_params *errors do
67
+ Message.create(params)
68
+ end
69
+ end
70
+ end
71
+
72
+ def test_create_via_aliases
73
+ assert_valid_message Message.create(
74
+ subscriber: @subscriber.phone_number,
75
+ body: 'addressed to @subscriber.phone_number'
76
+ )
77
+
78
+ assert_valid_message Message.create(
79
+ subscriber: @subscriber.sid,
80
+ transport: @transport.endpoint,
81
+ body: 'sent via @transport.endpoint'
82
+ )
83
+
84
+ assert_valid_message Message.create(
85
+ subscriber: @subscriber.phone_number,
86
+ channel: @channel.name,
87
+ body: 'sent via @channel.name'
88
+ )
89
+ end
90
+
91
+ def test_create_new_subscriber
92
+ next_phone = @channel.subscribers.map(&:phone_number).sort.last.to_i + 1
93
+
94
+ m = Message.create(
95
+ subscriber: format('%d', next_phone),
96
+ body: format('a message for %d', next_phone)
97
+ )
98
+
99
+ refute_equal m.subscriber_sid, @subscriber.sid, 'subscriber_sid must match'
100
+ assert_equal m.transport_sid, @transport.sid, 'transport_sid must match'
101
+ assert_equal m.channel_sid, @channel.sid, 'channel_sid must match'
102
+ end
103
+
104
+ def test_relations
105
+ message = @subscriber.messages.last
106
+
107
+ assert_equal @channel, message.channel
108
+ assert_equal @subscriber, message.subscriber
109
+ assert_equal @transport, message.transport
110
+ end
111
+
112
+ protected
113
+
114
+ def assert_valid_message(message)
115
+ assert_equal message.subscriber_sid, @subscriber.sid, 'subscriber_sid must match'
116
+ assert_equal message.transport_sid, @transport.sid, 'transport_sid must match'
117
+ assert_equal message.channel_sid, @channel.sid, 'channel_sid must match'
118
+ end
119
+ end
@@ -0,0 +1,27 @@
1
+ require 'test_helper'
2
+
3
+ class PropertyTest < EssentialTest
4
+ def setup
5
+ channel = Essential::Messaging::Channel.list.first
6
+ @sub_a = channel.subscribers.create(phone_number: '2221110000')
7
+ @sub_b = channel.subscribers.create(phone_number: '2221110001')
8
+ refute_equal @sub_a, @sub_b, 'test requires at least two subscribers'
9
+ end
10
+
11
+ def test_set_props
12
+ props_a = @sub_a.properties
13
+ existing_names = Set.new(props_a.map(&:name))
14
+
15
+ five_new = (5.times).map do
16
+ props_a.create(name: SecureRandom.uuid, value: SecureRandom.uuid)
17
+ end
18
+
19
+ new_names = Set.new(five_new.map(&:name))
20
+
21
+ assert (existing_names & new_names).empty?, 'new_names must be new'
22
+
23
+ props_a.refresh
24
+ existing_names = Set.new(props_a.map(&:name))
25
+ assert_equal (existing_names & new_names), new_names, 'must contain all new_names'
26
+ end
27
+ end
@@ -0,0 +1,106 @@
1
+ require 'test_helper'
2
+
3
+ class SubscriberTest < EssentialTest
4
+ include Essential::Messaging
5
+
6
+ def setup
7
+ @channel = Channel.list.first
8
+ end
9
+
10
+ def test_create
11
+ sub = Subscriber.create(
12
+ phone_number: '1002003000',
13
+ channel: @channel.name
14
+ )
15
+
16
+ assert_equal '1002003000', sub.phone_number
17
+ assert_equal @channel.sid, sub.channel_sid
18
+ end
19
+
20
+ def test_list
21
+ (1..4).each do |n|
22
+ Subscriber.create(phone_number: format('100200300%d', n))
23
+ end
24
+
25
+ subs = Subscriber.list
26
+ assert subs.size > 4, 'should have more than 4 subscribers'
27
+
28
+ sub_sids = Set.new(subs.map(&:sid))
29
+ assert_equal subs.size, sub_sids.size, 'must contain N unique sids'
30
+ end
31
+
32
+ def test_list_bad_channel_sid
33
+ error = assert_raises Essential::APIError do
34
+ Subscriber.list(params: {channel: :not_a_real_sid})
35
+ end
36
+
37
+ assert_equal 400, error.http_status
38
+ refute_nil error.params
39
+ assert error.params.key?('channel'), 'must complain about channel_sid'
40
+ end
41
+
42
+ def test_list_bad_channel_name
43
+ error = assert_raises Essential::APIError do
44
+ Subscriber.list(params: {channel: :not_a_real_name})
45
+ end
46
+
47
+ assert_equal 400, error.http_status
48
+ refute_nil error.params
49
+ assert error.params.key?('channel'), 'must complain about channel_name'
50
+ end
51
+
52
+ def test_update
53
+ sub = Subscriber.create(
54
+ phone_number: '9998887777',
55
+ channel: @channel.name
56
+ )
57
+ sid = sub.sid.dup
58
+
59
+ sub.update(phone_number: '7778889999')
60
+ assert_equal sid, sub.sid
61
+ assert_equal '7778889999', sub.phone_number
62
+
63
+ sub = Subscriber.retrieve(sid)
64
+ assert_equal sid, sub.sid
65
+ assert_equal '7778889999', sub.phone_number
66
+
67
+ error = assert_raises Essential::APIError do
68
+ duplicate = Subscriber.create(
69
+ phone_number: '1012023333',
70
+ channel: @channel.sid
71
+ )
72
+ duplicate.update(phone_number: sub.phone_number)
73
+ end
74
+ assert_equal 400, error.http_status
75
+ refute_nil error.params
76
+ assert error.params.key?('phone_number')
77
+
78
+ # change it back: if we're running a smoke test, these are
79
+ # unique and we need to make sure that '7778889999' doesn't
80
+ # exist at the start of the next run!
81
+ sub.update(phone_number: '9998887777')
82
+ assert_equal '9998887777', sub.phone_number
83
+ end
84
+
85
+ def test_unsubscribe
86
+ sub = Subscriber.create(
87
+ phone_number: '1002003000',
88
+ channel: @channel.name
89
+ )
90
+
91
+ response = sub.unsubscribe
92
+
93
+ Subscriber.retrieve(sub.sid)
94
+ end
95
+
96
+ def test_relations
97
+ sub = @channel.subscribers.first
98
+
99
+ assert_equal @channel, sub.channel
100
+ assert_equal sub.transport_sid, sub.transport.sid
101
+
102
+ mess = sub.messages
103
+ refute_equal 0, mess.count
104
+ assert mess.all? {|m| Message === m}
105
+ end
106
+ end