govdelivery-tms 0.8.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.
Files changed (65) hide show
  1. checksums.yaml +15 -0
  2. data/Gemfile +14 -0
  3. data/README.md +324 -0
  4. data/Rakefile +20 -0
  5. data/govdelivery-tms.gemspec +30 -0
  6. data/lib/govdelivery-tms.rb +43 -0
  7. data/lib/govdelivery-tms/base.rb +37 -0
  8. data/lib/govdelivery-tms/client.rb +97 -0
  9. data/lib/govdelivery-tms/collection_resource.rb +54 -0
  10. data/lib/govdelivery-tms/connection.rb +34 -0
  11. data/lib/govdelivery-tms/errors.rb +58 -0
  12. data/lib/govdelivery-tms/instance_resource.rb +219 -0
  13. data/lib/govdelivery-tms/link_header.rb +223 -0
  14. data/lib/govdelivery-tms/logger.rb +36 -0
  15. data/lib/govdelivery-tms/mail/delivery_method.rb +63 -0
  16. data/lib/govdelivery-tms/resource/collections.rb +98 -0
  17. data/lib/govdelivery-tms/resource/command.rb +25 -0
  18. data/lib/govdelivery-tms/resource/command_action.rb +17 -0
  19. data/lib/govdelivery-tms/resource/command_type.rb +20 -0
  20. data/lib/govdelivery-tms/resource/email_message.rb +81 -0
  21. data/lib/govdelivery-tms/resource/email_recipient.rb +31 -0
  22. data/lib/govdelivery-tms/resource/email_recipient_click.rb +8 -0
  23. data/lib/govdelivery-tms/resource/email_recipient_open.rb +8 -0
  24. data/lib/govdelivery-tms/resource/email_template.rb +15 -0
  25. data/lib/govdelivery-tms/resource/from_address.rb +10 -0
  26. data/lib/govdelivery-tms/resource/inbound_sms_message.rb +8 -0
  27. data/lib/govdelivery-tms/resource/ipaws_acknowledgement.rb +9 -0
  28. data/lib/govdelivery-tms/resource/ipaws_alert.rb +38 -0
  29. data/lib/govdelivery-tms/resource/ipaws_category.rb +7 -0
  30. data/lib/govdelivery-tms/resource/ipaws_cog_profile.rb +29 -0
  31. data/lib/govdelivery-tms/resource/ipaws_event_code.rb +7 -0
  32. data/lib/govdelivery-tms/resource/ipaws_nwem_area.rb +18 -0
  33. data/lib/govdelivery-tms/resource/ipaws_nwem_authorization.rb +9 -0
  34. data/lib/govdelivery-tms/resource/ipaws_nwem_auxilary_data.rb +8 -0
  35. data/lib/govdelivery-tms/resource/ipaws_response_type.rb +7 -0
  36. data/lib/govdelivery-tms/resource/ipaws_static_resource.rb +8 -0
  37. data/lib/govdelivery-tms/resource/keyword.rb +30 -0
  38. data/lib/govdelivery-tms/resource/recipient.rb +10 -0
  39. data/lib/govdelivery-tms/resource/sms_message.rb +35 -0
  40. data/lib/govdelivery-tms/resource/webhook.rb +20 -0
  41. data/lib/govdelivery-tms/util/core_ext.rb +27 -0
  42. data/lib/govdelivery-tms/util/hal_link_parser.rb +50 -0
  43. data/lib/govdelivery-tms/version.rb +3 -0
  44. data/spec/client_spec.rb +41 -0
  45. data/spec/command_types_spec.rb +29 -0
  46. data/spec/email_message_spec.rb +102 -0
  47. data/spec/email_template_spec.rb +149 -0
  48. data/spec/errors_spec.rb +13 -0
  49. data/spec/from_address_spec.rb +86 -0
  50. data/spec/inbound_sms_messages_spec.rb +19 -0
  51. data/spec/instance_resource_spec.rb +61 -0
  52. data/spec/ipaws_acknowledgement_spec.rb +16 -0
  53. data/spec/ipaws_alerts_spec.rb +192 -0
  54. data/spec/ipaws_cog_profile_spec.rb +75 -0
  55. data/spec/ipaws_event_codes_spec.rb +35 -0
  56. data/spec/ipaws_nwem_areas_spec.rb +58 -0
  57. data/spec/ipaws_nwem_authorization_spec.rb +16 -0
  58. data/spec/keyword_spec.rb +62 -0
  59. data/spec/keywords_spec.rb +21 -0
  60. data/spec/mail/delivery_method_spec.rb +52 -0
  61. data/spec/sms_message_spec.rb +63 -0
  62. data/spec/sms_messages_spec.rb +21 -0
  63. data/spec/spec_helper.rb +31 -0
  64. data/spec/tms_spec.rb +7 -0
  65. metadata +172 -0
@@ -0,0 +1,20 @@
1
+ module TMS #:nodoc:
2
+ # A Webhook gets invoked when a recipient enters a queued or final state
3
+ #
4
+ # @attr url [String] The URL to POST webhooks to
5
+ # @attr event_type 'sending', 'inconclusive', 'blacklisted', 'sent', 'canceled', or 'failed'
6
+ #
7
+ # @example
8
+ # webhook = client.webhooks.build(:url => 'http://your.url', :event_type => 'failed')
9
+ # webhook.post
10
+ # webhook.get
11
+ class Webhook
12
+ include InstanceResource
13
+
14
+ # @!parse attr_accessor :url, :event_type
15
+ writeable_attributes :url, :event_type
16
+
17
+ # @!parse attr_reader :created_at, :updated_at
18
+ readonly_attributes :created_at, :updated_at
19
+ end
20
+ end
@@ -0,0 +1,27 @@
1
+ require 'active_support/inflector'
2
+
3
+ module TMS::CoreExt
4
+ def demodulize(path)
5
+ ActiveSupport::Inflector.demodulize(path)
6
+ end
7
+
8
+ def classify(str)
9
+ ActiveSupport::Inflector.camelize(str)
10
+ end
11
+
12
+ def singularize(str)
13
+ ActiveSupport::Inflector.singularize(str)
14
+ end
15
+
16
+ def pluralize(str)
17
+ ActiveSupport::Inflector.pluralize(str)
18
+ end
19
+
20
+ def tmsify(klassname)
21
+ ActiveSupport::Inflector.underscore(demodulize(klassname))
22
+ end
23
+
24
+ def instance_class(klass)
25
+ ActiveSupport::Inflector.constantize(singularize(klass.to_s))
26
+ end
27
+ end
@@ -0,0 +1,50 @@
1
+ module TMS::Util
2
+ module HalLinkParser
3
+
4
+ def parse_links(_links)
5
+ @resources = {}
6
+ return if _links.nil?
7
+ parse_link(_links) and return if _links.is_a?(Hash)
8
+ _links.each { |link| parse_link(link) }
9
+ end
10
+
11
+ def subresources
12
+ @resources
13
+ end
14
+
15
+ protected
16
+
17
+ def metaclass
18
+ @metaclass ||= class << self;
19
+ self;
20
+ end
21
+ end
22
+
23
+ def parse_link(link)
24
+ link.each do |rel, href|
25
+ if rel == 'self'
26
+ self.href = href
27
+ else
28
+ klass = relation_class(rel)
29
+ klass = self.class if ['first', 'prev', 'next', 'last'].include?(rel)
30
+ if klass
31
+ subresources[rel] = klass.new(self.client, href)
32
+ setup_subresource(link)
33
+ else
34
+ logger.info("Don't know what to do with link rel '#{rel}' for class #{self.class.to_s}!") if self.respond_to?(:logger)
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+
41
+ def relation_class(rel)
42
+ ::TMS.const_get(classify(rel)) rescue nil
43
+ end
44
+
45
+ def setup_subresource(link)
46
+ return unless link
47
+ link.each { |rel, href| self.metaclass.send(:define_method, rel.to_sym, &lambda { subresources[rel] }) unless rel == 'self' }
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,3 @@
1
+ module TMS #:nodoc:
2
+ VERSION = "0.8.0"
3
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+ describe TMS::Client do
3
+ context "creating a new client" do
4
+ before do
5
+ response = double('response', :status => 200, :body => {"_links" => [{"self" => "/"}, {"horse" => "/horses/new"}, {"rabbits" => "/rabbits"}]})
6
+ @raw_connection = double('raw_connection', :get => response)
7
+ @connection = TMS::Connection.stub(:new).and_return(double('connection', :connection => @raw_connection))
8
+ @client = TMS::Client.new('auth_token', :api_root => 'null_url')
9
+ end
10
+ it 'should set up logging' do
11
+ @client.logger.should_not be_nil
12
+ @client.logger.level.should eq(Logger::INFO)
13
+ end
14
+ it 'should discover endpoints for known services' do
15
+ @client.horse.should be_kind_of(TMS::Horse)
16
+ @client.rabbits.should be_kind_of(TMS::Rabbits)
17
+ end
18
+ it 'should handle 4xx responses' do
19
+ @raw_connection.stub(:get).and_return(double('response', :status => 404, :body => {'message' => 'hi'}))
20
+ expect { @client.get('/blargh') }.to raise_error(TMS::Request::Error)
21
+ end
22
+ it 'should handle 5xx responses' do
23
+ @raw_connection.stub(:get).and_return(double('response', :status => 503, :body => {'message' => 'oops'}))
24
+ expect { @client.get('/blargh') }.to raise_error(TMS::Request::Error)
25
+ end
26
+ it 'should handle 202 responses' do
27
+ @raw_connection.stub(:get).and_return(double('response', :status => 202, :body => {'message' => 'hi'}))
28
+ expect { @client.get('/blargh') }.to raise_error(TMS::Request::InProgress)
29
+ end
30
+
31
+ context 'creating a new client without output' do
32
+ subject { TMS::Client.new('auth_token', api_root: 'null_url', logger: false) }
33
+ its(:logger){ should be_falsey }
34
+ its(:horse) { should be_kind_of(TMS::Horse) }
35
+ end
36
+
37
+ it 'defaults to the public API URL' do
38
+ expect(TMS::Client.new('auth_token').api_root).to eq('https://tms.govdelivery.com')
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe TMS::CommandTypes do
4
+ context "loading command types" do
5
+ let(:client) do
6
+ double('client')
7
+ end
8
+ before do
9
+ @command_types = TMS::CommandTypes.new(client, '/command_types')
10
+ end
11
+ it 'should GET ok' do
12
+ body = [{"name"=>"dcm_unsubscribe",
13
+ "string_fields"=>[],
14
+ "array_fields"=>["dcm_account_codes"]},
15
+ {"name"=>"dcm_subscribe",
16
+ "string_fields"=>["dcm_account_code"],
17
+ "array_fields"=>["dcm_topic_codes"]},
18
+ {"name"=>"forward",
19
+ "string_fields"=>["http_method", "username", "password", "url"],
20
+ "array_fields"=>[]}]
21
+ @command_types.client.should_receive(:get).and_return(double('response', :body => body, :status => 200, :headers => {}))
22
+ @command_types.get
23
+ @command_types.collection.length.should == 3
24
+ ct = @command_types.collection.find{|c| c.name == "dcm_subscribe"}
25
+ ct.array_fields.should eq(["dcm_topic_codes"])
26
+ ct.string_fields.should eq(["dcm_account_code"])
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+
3
+ describe TMS::EmailMessage do
4
+ context "creating a new message" do
5
+ let(:client) do
6
+ double('client')
7
+ end
8
+ before do
9
+ @message = TMS::EmailMessage.new(client, '/messages/email', {
10
+ :body => '12345678',
11
+ :subject => 'blah',
12
+ :created_at => 'BAAAAAD',
13
+ :from_email => 'eric@evotest.govdelivery.com',
14
+ :errors_to => 'errors@evotest.govdelivery.com',
15
+ :reply_to => 'replyto@evotest.govdelivery.com'})
16
+ end
17
+ it 'should not render readonly attrs in json hash' do
18
+ @message.to_json[:body].should == '12345678'
19
+ @message.to_json[:created_at].should == nil
20
+ end
21
+ it 'should initialize with attrs and collections' do
22
+ @message.body.should == '12345678'
23
+ @message.subject.should == 'blah'
24
+ @message.from_email.should == 'eric@evotest.govdelivery.com'
25
+ @message.reply_to.should == 'replyto@evotest.govdelivery.com'
26
+ @message.errors_to.should == 'errors@evotest.govdelivery.com'
27
+ @message.recipients.class.should == TMS::EmailRecipients
28
+ end
29
+ it 'should post successfully' do
30
+ response = {
31
+ :body => 'processed',
32
+ :subject => 'blah',
33
+ :from_email => 'eric@evotest.govdelivery.com',
34
+ :errors_to => 'errors@evotest.govdelivery.com',
35
+ :reply_to => 'replyto@evotest.govdelivery.com',
36
+ :recipients => [{:email => 'billy@evotest.govdelivery.com'}],
37
+ :failed => [{:email => 'billy@evotest.govdelivery.com'}],
38
+ :sent => [{:email => 'billy@evotest.govdelivery.com'}],
39
+ :created_at => 'time'
40
+ }
41
+ @message.client.should_receive('post').with(@message).and_return(double('response', :status => 201, :body => response))
42
+ @message.post
43
+ @message.body.should == 'processed'
44
+ @message.created_at.should == 'time'
45
+ @message.from_email.should == 'eric@evotest.govdelivery.com'
46
+ @message.reply_to.should == 'replyto@evotest.govdelivery.com'
47
+ @message.errors_to.should == 'errors@evotest.govdelivery.com'
48
+ @message.recipients.class.should == TMS::EmailRecipients
49
+ @message.recipients.collection.first.class.should == TMS::EmailRecipient
50
+ @message.sent.class.should == TMS::EmailRecipients
51
+ @message.sent.collection.first.class.should == TMS::EmailRecipient
52
+ @message.failed.class.should == TMS::EmailRecipients
53
+ @message.failed.collection.first.class.should == TMS::EmailRecipient
54
+ end
55
+ it 'should handle errors' do
56
+ response = {'errors' => {:body => "can't be nil"}}
57
+ @message.client.should_receive('post').with(@message).and_return(double('response', :status => 422, :body => response))
58
+ @message.post
59
+ @message.body.should == '12345678'
60
+ @message.errors.should == {:body => "can't be nil"}
61
+ end
62
+
63
+ it 'should handle 401 errors' do
64
+ @message.client.should_receive('post').with(@message).and_return(double('response', :status => 401))
65
+ expect {@message.post}.to raise_error(StandardError, "401 Not Authorized")
66
+ end
67
+
68
+ it 'should handle 404 errors' do
69
+ @message.client.should_receive('post').with(@message).and_return(double('response', :status => 404))
70
+ expect {@message.post}.to raise_error(StandardError, "Can't POST to /messages/email")
71
+ end
72
+ end
73
+
74
+ context 'an existing message' do
75
+ let(:client) do
76
+ double('client')
77
+ end
78
+ before do
79
+ # blank hash prevents the client from doing a GET in the initialize method
80
+ @message = TMS::EmailMessage.new(client, '/messages/99', {})
81
+ end
82
+ it 'should GET cleanly' do
83
+ response = {:body => 'processed',
84
+ :subject => 'hey',
85
+ :from_email => 'eric@evotest.govdelivery.com',
86
+ :errors_to => 'errors@evotest.govdelivery.com',
87
+ :reply_to => 'replyto@evotest.govdelivery.com',
88
+ :recipients => [{:email => 'billy@evotest.govdelivery.com'}],
89
+ :created_at => 'time'}
90
+ @message.client.should_receive('get').with(@message.href).and_return(double('response', :status => 200, :body => response))
91
+ @message.get
92
+ @message.body.should == 'processed'
93
+ @message.subject.should == 'hey'
94
+ @message.from_email.should == 'eric@evotest.govdelivery.com'
95
+ @message.reply_to.should == 'replyto@evotest.govdelivery.com'
96
+ @message.errors_to.should == 'errors@evotest.govdelivery.com'
97
+ @message.created_at.should == 'time'
98
+ end
99
+ end
100
+
101
+
102
+ end
@@ -0,0 +1,149 @@
1
+ require 'spec_helper'
2
+
3
+ describe TMS::EmailTemplate do
4
+ context "creating a list of email templates" do
5
+ let(:client) do
6
+ double('client')
7
+ end
8
+ before do
9
+ @templates = TMS::EmailTemplates.new(client, '/templates/email')
10
+ end
11
+
12
+ it 'should be able to get a list of email templates' do
13
+ response = [
14
+ {
15
+ 'id' => "1",
16
+ 'body' => "Template 1",
17
+ 'subject' => "This is the template 1 subject",
18
+ 'link_tracking_parameters' => "test=ok&hello=world",
19
+ 'macros' => {"MACRO1" => "1"},
20
+ 'open_tracking_enabled' => true,
21
+ 'click_tracking_enabled' => true,
22
+ 'created_at' => "sometime",
23
+ '_links' => {"self" => "/templates/email/1","account" => "/accounts/1","from_address" => "/from_addresses/1"}
24
+ }
25
+ ]
26
+
27
+ @templates.client.should_receive('get').with('/templates/email').and_return(double('response', :status => 200, :body => response, :headers => {}))
28
+ @templates.get
29
+ @templates.collection.length.should == 1
30
+ end
31
+ end
32
+
33
+ context "creating an email template" do
34
+ let(:client) do
35
+ double('client')
36
+ end
37
+ before do
38
+ @template = TMS::EmailTemplate.new(client, '/templates/email', {
39
+ :body => "Template 1",
40
+ :subject => "This is the template 1 subject",
41
+ :link_tracking_parameters => "test=ok&hello=world",
42
+ :macros => {"MACRO1" => "1"},
43
+ :open_tracking_enabled => true,
44
+ :click_tracking_enabled => true,
45
+ })
46
+ end
47
+
48
+ it 'should render linkable attrs in json hash' do
49
+ @template.links[:from_address] = "1"
50
+ @template.links[:invalid] = "2"
51
+ links = @template.to_json[:_links]
52
+ links[:from_address].should == '1'
53
+ links[:invalid].should be_nil
54
+ end
55
+
56
+ it 'should clear the links property after a successful post' do
57
+ @template.links[:from_address] = "1"
58
+ @template.client.should_receive('post').with(@template).and_return(double('response', :status => 201, :body => {}))
59
+ @template.post
60
+ links = @template.to_json[:_links]
61
+ links[:from_address].should be_nil
62
+ end
63
+
64
+ it 'should not clear the links property after an invalid post' do
65
+ @template.links[:from_address] = "1"
66
+ @template.client.should_receive('post').with(@template).and_return(double('response', :status => 400, :body => {}))
67
+ @template.post
68
+ links = @template.to_json[:_links]
69
+ links[:from_address].should == "1"
70
+ end
71
+
72
+ it 'should post successfully' do
73
+ response = {
74
+ 'id' => "1",
75
+ 'body' => "Template 1",
76
+ 'subject' => "This is the template 1 subject",
77
+ 'link_tracking_parameters' => "test=ok&hello=world",
78
+ 'macros' => {"MACRO1" => "1"},
79
+ 'open_tracking_enabled' => true,
80
+ 'click_tracking_enabled' => true,
81
+ 'created_at' => "sometime",
82
+ '_links' => {"self" => "/templates/email/1","account" => "/accounts/1","from_address" => "/from_addresses/1"}
83
+ }
84
+ @template.client.should_receive('post').with(@template).and_return(double('response', :status => 201, :body => response))
85
+ @template.post
86
+ @template.id.should == '1'
87
+ @template.body.should == 'Template 1'
88
+ @template.subject.should == 'This is the template 1 subject'
89
+ @template.link_tracking_parameters.should == 'test=ok&hello=world'
90
+ @template.macros.should == {"MACRO1"=>"1"}
91
+ @template.open_tracking_enabled.should == true
92
+ @template.click_tracking_enabled.should == true
93
+ @template.created_at.should == 'sometime'
94
+ @template.from_address.should be_a(TMS::FromAddress)
95
+ end
96
+ end
97
+
98
+ context "handling errors at the template level" do
99
+ let(:client) do
100
+ double('client')
101
+ end
102
+ before do
103
+ @template = TMS::EmailTemplate.new(client, '/templates/email/1')
104
+ end
105
+
106
+ it 'should handle errors' do
107
+ response = {'errors' => {:body => "can't be nil"}}
108
+ @template.client.should_receive('post').with(@template).and_return(double('response', :status => 422, :body => response))
109
+ @template.post
110
+ @template.errors.should == {:body => "can't be nil"}
111
+ end
112
+
113
+ it 'should handle 401 errors' do
114
+ @template.client.should_receive('post').with(@template).and_return(double('response', :status => 401))
115
+ expect {@template.post}.to raise_error("401 Not Authorized")
116
+ end
117
+
118
+ it 'should handle 404 errors' do
119
+ @template.client.should_receive('post').with(@template).and_return(double('response', :status => 404))
120
+ expect {@template.post}.to raise_error("Can't POST to /templates/email/1")
121
+ end
122
+ end
123
+
124
+ context "handling errors at the email_templates root level" do
125
+ let(:client) do
126
+ double('client')
127
+ end
128
+ before do
129
+ @template = TMS::EmailTemplate.new(client, '/templates/email')
130
+ end
131
+
132
+ it 'should handle errors' do
133
+ response = {'errors' => {:body => "can't be nil"}}
134
+ @template.client.should_receive('post').with(@template).and_return(double('response', :status => 422, :body => response))
135
+ @template.post
136
+ @template.errors.should == {:body => "can't be nil"}
137
+ end
138
+
139
+ it 'should handle 401 errors' do
140
+ @template.client.should_receive('post').with(@template).and_return(double('response', :status => 401))
141
+ expect {@template.post}.to raise_error("401 Not Authorized")
142
+ end
143
+
144
+ it 'should handle 404 errors' do
145
+ @template.client.should_receive('post').with(@template).and_return(double('response', :status => 404))
146
+ expect {@template.post}.to raise_error("Can't POST to /templates/email")
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe TMS::Errors do
4
+ context "an errors hash" do
5
+ let(:object_with_errors) do
6
+ double('instance', href: 'href', errors: {"body" => ["can't be blank"], "subject" => ["can't be blank"]})
7
+ end
8
+ subject { TMS::Errors::InvalidVerb.new(object_with_errors) }
9
+ it 'should work' do
10
+ subject.message.should =~ /body can't be blank, subject can't be blank/
11
+ end
12
+ end
13
+ end