govdelivery-tms 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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