postageapp 1.3.0 → 1.4.2

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 (72) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +6 -0
  3. data/.travis.yml +9 -39
  4. data/{LICENSE → LICENSE.md} +1 -1
  5. data/README.md +117 -13
  6. data/Rakefile +15 -4
  7. data/VERSION +1 -0
  8. data/app/ingresses/action_mailbox/ingresses/postage_app/inbound_emails_controller.rb +52 -0
  9. data/app/ingresses/action_mailbox/ingresses/postage_app.rb +4 -0
  10. data/config/routes.rb +8 -0
  11. data/exe/postageapp +37 -0
  12. data/lib/generators/postageapp/postageapp_generator.rb +9 -6
  13. data/lib/postageapp/cli/command/config.rb +74 -0
  14. data/lib/postageapp/cli/command/create_mailbox.rb +21 -0
  15. data/lib/postageapp/cli/command/env.rb +58 -0
  16. data/lib/postageapp/cli/command/get_project_info.rb +3 -0
  17. data/lib/postageapp/cli/command.rb +110 -0
  18. data/lib/postageapp/cli.rb +14 -0
  19. data/lib/postageapp/configuration.rb +237 -74
  20. data/lib/postageapp/engine.rb +11 -0
  21. data/lib/postageapp/env.rb +9 -0
  22. data/lib/postageapp/mailer/mailer_4.rb +30 -14
  23. data/lib/postageapp/mailer.rb +1 -11
  24. data/lib/postageapp/rails/railtie.rb +1 -3
  25. data/lib/postageapp/request.rb +6 -1
  26. data/lib/postageapp.rb +53 -35
  27. data/log/.gitignore +1 -0
  28. data/postageapp.gemspec +7 -10
  29. data/script/with +2 -2
  30. data/test/gemfiles/Gemfile.rails-2.3.x +1 -1
  31. data/test/gemfiles/Gemfile.rails-3.0.x +1 -1
  32. data/test/gemfiles/Gemfile.rails-3.1.x +1 -1
  33. data/test/gemfiles/Gemfile.rails-3.2.x +1 -1
  34. data/test/gemfiles/Gemfile.rails-4.0.x +1 -1
  35. data/test/gemfiles/Gemfile.rails-4.1.x +1 -1
  36. data/test/gemfiles/Gemfile.rails-4.2.x +1 -2
  37. data/test/gemfiles/Gemfile.rails-5.0.x +2 -3
  38. data/test/gemfiles/Gemfile.rails-5.2.x +12 -0
  39. data/test/gemfiles/Gemfile.rails-6.0.x +12 -0
  40. data/test/gemfiles/Gemfile.rails-6.1.x +12 -0
  41. data/test/gemfiles/Gemfile.ruby +2 -3
  42. data/test/helper.rb +5 -3
  43. data/test/log/.gitignore +1 -0
  44. data/test/mailer/action_mailer_3/notifier.rb +1 -1
  45. data/test/tmp/.gitignore +1 -0
  46. data/test/travis_test.rb +58 -40
  47. data/test/{configuration_test.rb → unit/configuration_test.rb} +18 -12
  48. data/test/{failed_request_test.rb → unit/failed_request_test.rb} +6 -6
  49. data/test/{live_test.rb → unit/live_test.rb} +4 -39
  50. data/test/{mail_delivery_method_test.rb → unit/mail_delivery_method_test.rb} +1 -1
  51. data/test/{mailer_4_test.rb → unit/mailer_4_test.rb} +2 -2
  52. data/test/{mailer_helper_methods_test.rb → unit/mailer_helper_methods_test.rb} +4 -4
  53. data/test/{postageapp_test.rb → unit/postageapp_test.rb} +7 -1
  54. data/test/{rails_initialization_test.rb → unit/rails_initialization_test.rb} +2 -2
  55. data/test/{request_test.rb → unit/request_test.rb} +18 -17
  56. data/test/{response_test.rb → unit/response_test.rb} +4 -4
  57. data/test/unit/tmp/.gitignore +1 -0
  58. data/tmp/.gitignore +1 -0
  59. metadata +41 -48
  60. data/lib/postageapp/mailer/mailer_2.rb +0 -140
  61. data/lib/postageapp/mailer/mailer_3.rb +0 -190
  62. data/lib/postageapp/version.rb +0 -3
  63. data/test/mailer/action_mailer_2/notifier/with_body_and_attachment.erb +0 -1
  64. data/test/mailer/action_mailer_2/notifier/with_custom_postage_variables.text.html.erb +0 -1
  65. data/test/mailer/action_mailer_2/notifier/with_custom_postage_variables.text.plain.erb +0 -1
  66. data/test/mailer/action_mailer_2/notifier/with_html_and_text_views.text.html.erb +0 -1
  67. data/test/mailer/action_mailer_2/notifier/with_html_and_text_views.text.plain.erb +0 -1
  68. data/test/mailer/action_mailer_2/notifier/with_simple_view.erb +0 -1
  69. data/test/mailer/action_mailer_2/notifier/with_text_only_view.text.plain.erb +0 -1
  70. data/test/mailer/action_mailer_2/notifier.rb +0 -77
  71. data/test/mailer_2_test.rb +0 -95
  72. data/test/mailer_3_test.rb +0 -118
@@ -0,0 +1,21 @@
1
+ PostageApp::CLI::Command.define do
2
+ api_key :account
3
+
4
+ argument :uid,
5
+ optional: true,
6
+ desc: 'An identifier to refer to this mailbox on subsequent API calls'
7
+ argument :label,
8
+ optional: true,
9
+ desc: 'A descriptive name for this mailbox'
10
+ argument :host,
11
+ desc: 'IMAP server hostname'
12
+ argument :port,
13
+ optional: true,
14
+ desc: 'IMAP server port (default 993)'
15
+ argument :username,
16
+ desc: 'Username/email-address used to authenticate with the IMAP server'
17
+ argument :password,
18
+ desc: 'Password used to authenticate with the IMAP server'
19
+ argument :postback_url,
20
+ desc: 'The URL to post received email content to'
21
+ end
@@ -0,0 +1,58 @@
1
+ PostageApp::CLI::Command.define do
2
+ argument :'no-header',
3
+ optional: true,
4
+ boolean: true,
5
+ desc: 'Suppress display of header'
6
+
7
+ argument :markdown,
8
+ optional: true,
9
+ boolean: true,
10
+ desc: 'Emit markdown formatted description of variables'
11
+
12
+ perform do |arguments|
13
+ if (arguments[:markdown])
14
+ PostageApp::Configuration.params.each do |param, config|
15
+ config[:env_vars]&.each_with_index do |var, i|
16
+ case (i)
17
+ when 0
18
+ case (default = config[:default])
19
+ when Proc
20
+ default = default.call
21
+ end
22
+
23
+ puts '* `%s`: %s (%s)' % [
24
+ var,
25
+ config[:desc],
26
+ case (config[:required])
27
+ when String
28
+ 'required %s' % config[:required]
29
+ when true
30
+ 'required'
31
+ else
32
+ default ? 'default: `%s`' % default : 'optional'
33
+ end
34
+ ]
35
+ else
36
+ puts '* `%s`: Alias for `%s`' % [
37
+ var,
38
+ config[:env_vars][0]
39
+ ]
40
+ end
41
+ end
42
+ end
43
+ else
44
+ unless (arguments[:'no-header'])
45
+ puts '%-40s %s' % [ 'Variable', 'Setting' ]
46
+ puts '-' * 78
47
+ end
48
+
49
+ PostageApp::Configuration.params.each do |param, config|
50
+ config[:env_vars]&.each do |var|
51
+ puts '%-40s %s' % [
52
+ var, ENV[var]
53
+ ]
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,3 @@
1
+ PostageApp::CLI::Command.define do
2
+ # No arguments required for this command
3
+ end
@@ -0,0 +1,110 @@
1
+ require 'optparse'
2
+
3
+ class PostageApp::CLI::Command
4
+ class APICallError < StandardError
5
+ end
6
+
7
+ class MissingArguments < StandardError
8
+ end
9
+
10
+ def self.defined
11
+ @defined ||= { }
12
+ end
13
+
14
+ def self.define(command_name = nil, &block)
15
+ command_name ||= $command_name
16
+ command = self.defined[command_name] = new(command_name)
17
+
18
+ command.instance_eval(&block) if (block_given?)
19
+ end
20
+
21
+ def initialize(command_name)
22
+ @command_name = command_name
23
+ @api_key_context = :project
24
+ @argument = { }
25
+ end
26
+
27
+ def api_key(context)
28
+ @api_key_context = context
29
+ end
30
+
31
+ def argument(name, optional: false, type: String, desc: nil, boolean: false)
32
+ @argument[name] = {
33
+ optional: optional,
34
+ type: String,
35
+ desc: desc,
36
+ boolean: boolean
37
+ }
38
+ end
39
+
40
+ def perform(&block)
41
+ @perform = block
42
+ end
43
+
44
+ def parse!(*args)
45
+ arguments = { }
46
+
47
+ op = OptionParser.new do |parser|
48
+ parser.banner = "Usage: postageapp #{@command_name} [options]"
49
+
50
+ @argument.each do |name, attributes|
51
+ if (attributes[:boolean])
52
+ parser.on("--#{name}", attributes[:desc]) do
53
+ arguments[name] = true
54
+ end
55
+ else
56
+ parser.on("--#{name} VALUE", "#{attributes[:desc]} (#{attributes[:optional] ? 'optional' : 'required'})") do |v|
57
+ arguments[name] = v
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ op.parse!(args)
64
+
65
+ missing = @argument.select do |name, attributes|
66
+ !attributes[:optional] && arguments[name].nil?
67
+ end.keys
68
+
69
+ if (missing.any?)
70
+ $stderr.puts("Error: missing options #{missing.join(', ')}")
71
+
72
+ puts op.help
73
+
74
+ raise MissingArguments
75
+ end
76
+
77
+ case (@api_key_context)
78
+ when :account
79
+ arguments['api_key'] = PostageApp.configuration.account_api_key
80
+ end
81
+
82
+ case (@perform&.arity)
83
+ when 1
84
+ return @perform.call(arguments)
85
+ when 0
86
+ return @perform.call
87
+ end
88
+
89
+ response = PostageApp::Request.new(@command_name, arguments).send
90
+
91
+ case (response.status)
92
+ when "ok"
93
+ puts JSON.pretty_generate(response.data)
94
+ else
95
+ $stderr.puts("Received error: #{response.status}")
96
+
97
+ if (response.message)
98
+ $stderr.puts(' ' + response.message)
99
+ end
100
+
101
+ raise APICallError
102
+ end
103
+ end
104
+ end
105
+
106
+ Dir.glob(File.expand_path('./command/*.rb', __dir__)) do |command|
107
+ $command_name = File.basename(command, '.rb').to_sym
108
+
109
+ require command
110
+ end
@@ -0,0 +1,14 @@
1
+ module PostageApp::CLI
2
+ class UnknownCommand < StandardError
3
+ end
4
+
5
+ def self.parse!(command_name, *args)
6
+ if (command = PostageApp::CLI::Command.defined[command_name.to_sym])
7
+ command.parse!(*args)
8
+ else
9
+ raise UnknownCommand, "The command #{command_name.inspect} is not known."
10
+ end
11
+ end
12
+ end
13
+
14
+ require_relative './cli/command'
@@ -32,8 +32,8 @@
32
32
  # -------------
33
33
  # :proxy_host - Proxy server hostname
34
34
  # :proxy_port - Proxy server port
35
- # :proxy_user - Proxy server username
36
- # :proxy_pass - Proxy server password
35
+ # :proxy_username - Proxy server username
36
+ # :proxy_password - Proxy server password
37
37
 
38
38
  # Advanced Options
39
39
  # ----------------
@@ -45,8 +45,6 @@
45
45
  class PostageApp::Configuration
46
46
  # == Constants ============================================================
47
47
 
48
- HOST_DEFAULT = 'api.postageapp.com'.freeze
49
-
50
48
  SOCKS5_PORT_DEFAULT = 1080
51
49
  HTTP_PORT_DEFAULT = 80
52
50
  HTTPS_PORT_DEFAULT = 443
@@ -56,96 +54,254 @@ class PostageApp::Configuration
56
54
  false => 'http'.freeze
57
55
  }.freeze
58
56
 
59
- PATH_DEFAULT = '/'.freeze
60
-
61
- FRAMEWORK_DEFAULT = 'Ruby'.freeze
62
- ENVIRONMENT_DEFAULT = 'production'.freeze
57
+ CONFIG_PARAMS = {
58
+ api_key: {
59
+ default: nil,
60
+ desc: 'Project API key to use',
61
+ required: 'for project API functions'
62
+ },
63
+ account_api_key: {
64
+ default: nil,
65
+ desc: 'Account API key to use',
66
+ required: 'for account API functions'
67
+ },
68
+ postback_secret: {
69
+ default: nil,
70
+ desc: 'Secret to use for validating ActionMailbox requests'
71
+ },
72
+ project_root: {
73
+ default: -> {
74
+ if (defined?(Rails) and Rails.respond_to?(:root))
75
+ Rails.root
76
+ else
77
+ Dir.pwd
78
+ end
79
+ },
80
+ desc: 'Project root for logging purposes'
81
+ },
82
+ recipient_override: {
83
+ default: nil,
84
+ interrogator: true,
85
+ desc: 'Override sender on `send_message` calls'
86
+ },
87
+ logger: {
88
+ default: nil,
89
+ env: false,
90
+ desc: 'Logger instance to use'
91
+ },
92
+ secure: {
93
+ default: true,
94
+ interrogator: true,
95
+ env: false,
96
+ after_set: -> (config) {
97
+ if (config.secure?)
98
+ config.protocol = 'https'
99
+ if (config.port == 80)
100
+ config.port = 443
101
+ end
102
+ else
103
+ config.protocol = 'http'
104
+ if (config.port == 443)
105
+ config.port = 80
106
+ end
107
+ end
108
+ },
109
+ desc: 'Enable verifying TLS connections'
110
+ },
111
+ verify_tls: {
112
+ default: true,
113
+ aliases: [ :verify_certificate ],
114
+ interrogator: true,
115
+ parse: -> (v) {
116
+ case (v)
117
+ when 'true', 'yes', 'on'
118
+ true
119
+ when String
120
+ v.to_i != 0
121
+ else
122
+ !!v
123
+ end
124
+ },
125
+ desc: 'Enable TLS certificate verification'
126
+ },
127
+ host: {
128
+ default: 'api.postageapp.com'.freeze,
129
+ desc: 'API host to contact'
130
+ },
131
+ port: {
132
+ default: 443,
133
+ desc: 'API port to contact'
134
+ },
135
+ scheme: {
136
+ default: 'https'.freeze,
137
+ aliases: [ :protocol ],
138
+ desc: 'HTTP scheme to use'
139
+ },
140
+ proxy_username: {
141
+ default: nil,
142
+ aliases: [ :proxy_user ],
143
+ desc: 'SOCKS5 proxy username'
144
+ },
145
+ proxy_password: {
146
+ default: nil,
147
+ aliases: [ :proxy_pass ],
148
+ desc: 'SOCKS5 proxy password'
149
+ },
150
+ proxy_host: {
151
+ default: nil,
152
+ desc: 'SOCKS5 proxy host'
153
+ },
154
+ proxy_port: {
155
+ default: 1080,
156
+ parse: -> (v) { v.to_i },
157
+ desc: 'SOCKS5 proxy port'
158
+ },
159
+ open_timeout: {
160
+ default: 5,
161
+ aliases: [ :http_open_timeout ],
162
+ parse: -> (v) { v.to_i },
163
+ desc: 'Timeout in seconds when initiating requests'
164
+ },
165
+ read_timeout: {
166
+ default: 10,
167
+ aliases: [ :http_read_timeout ],
168
+ parse: -> (v) { v.to_i },
169
+ desc: 'Timeout in seconds when awaiting responses'
170
+ },
171
+ retry_methods: {
172
+ default: %w[ send_message ].freeze,
173
+ aliases: [ :requests_to_resend ],
174
+ parse: -> (v) {
175
+ case (v)
176
+ when String
177
+ v.split(/\s*(?:,|\s)\s*/).grep(/\S/)
178
+ else
179
+ v
180
+ end
181
+ },
182
+ desc: 'Which API calls to retry, comma and/or space separated'
183
+ },
184
+ framework: {
185
+ default: -> {
186
+ if (defined?(Rails) and Rails.respond_to?(:version))
187
+ 'Ruby %s / Ruby on Rails %s' % [
188
+ RUBY_VERSION,
189
+ Rails.version
190
+ ]
191
+ else
192
+ 'Ruby %s' % RUBY_VERSION
193
+ end
194
+ },
195
+ desc: 'Framework used'
196
+ },
197
+ environment: {
198
+ default: 'production',
199
+ desc: 'Environment to use'
200
+ }
201
+ }.freeze
63
202
 
64
203
  # == Properties ===========================================================
65
204
 
66
- attr_accessor :secure
67
- attr_writer :scheme
68
- attr_accessor :host
69
- attr_writer :port
70
- attr_accessor :proxy_host
71
- attr_writer :proxy_port
72
- attr_accessor :proxy_user
73
- attr_accessor :proxy_pass
74
-
75
- attr_accessor :verify_certificate
76
- attr_accessor :http_open_timeout
77
- attr_accessor :http_read_timeout
78
- attr_accessor :recipient_override
79
- attr_accessor :requests_to_resend
80
- attr_accessor :project_root
81
- attr_accessor :framework
82
- attr_accessor :environment
83
- attr_accessor :logger
205
+ CONFIG_PARAMS.each do |param, config|
206
+ attr_reader param
84
207
 
85
- # == Instance Methods =====================================================
86
-
87
- def initialize
88
- @secure = true
89
- @verify_certificate = true
208
+ ivar = config[:ivar] ||= :"@#{param}"
209
+ mutator_method = :"#{param}="
210
+ config[:sources] = [ param ]
211
+ after_set = config[:after_set]
212
+
213
+ if (parser = config[:parse])
214
+ define_method(mutator_method) do |v|
215
+ instance_variable_set(ivar, parser.call(v))
216
+ end
217
+ else
218
+ define_method(mutator_method) do |v|
219
+ instance_variable_set(ivar, v)
90
220
 
91
- @host = ENV['POSTAGEAPP_HOST'] || HOST_DEFAULT
221
+ after_set and after_set[self]
222
+ end
223
+ end
92
224
 
93
- @proxy_port = SOCKS5_PORT_DEFAULT
225
+ interrogator_method = nil
94
226
 
95
- @http_open_timeout = 5
96
- @http_read_timeout = 10
227
+ if (config[:interrogator])
228
+ interrogator_method = :"#{param}?"
229
+ define_method(interrogator_method) do
230
+ !!instance_variable_get(ivar)
231
+ end
232
+ end
97
233
 
98
- @requests_to_resend = %w[ send_message ]
234
+ if (param_aliases = config[:aliases])
235
+ param_aliases.each do |param_alias|
236
+ config[:sources] << param_alias
99
237
 
100
- @framework = FRAMEWORK_DEFAULT
101
- @environment = ENVIRONMENT_DEFAULT
102
- end
103
-
104
- alias_method :secure?, :secure
105
- alias_method :verify_certificate?, :verify_certificate
238
+ alias_method param_alias, param
239
+ alias_method :"#{param_alias}=", mutator_method
106
240
 
107
- def port_default?
108
- if (self.secure?)
109
- self.port == HTTPS_PORT_DEFAULT
110
- else
111
- self.port == HTTP_PORT_DEFAULT
241
+ if (config[:interrogator])
242
+ alias_method :"#{param_alias}?", interrogator_method
243
+ end
244
+ end
112
245
  end
113
- end
114
246
 
115
- def proxy?
116
- self.proxy_host and self.proxy_host.match(/\A\S+\z/)
117
- end
247
+ unless (config[:env] === false)
248
+ config[:env_vars] = config[:sources].map do |source|
249
+ 'POSTAGEAPP_' + source.to_s.upcase
250
+ end
251
+ end
118
252
 
119
- # Assign which API key is used to make API calls. Can also be specified
120
- # using the `POSTAGEAPP_API_KEY` environment variable.
121
- def api_key=(key)
122
- @api_key = key
253
+ # config[:getters] = config[:sources].map do |source|
254
+ # -> (credentials) { credentials[:source] }
255
+ # end + config[:env_vars].map do |var|
256
+ # -> (_) { ENV[var] }
257
+ # end
123
258
  end
124
-
125
- # Returns the API key used to make API calls. Can be specified as the
126
- # `POSTAGEAPP_API_KEY` environment variable.
127
- def api_key
128
- @api_key ||= ENV['POSTAGEAPP_API_KEY']
259
+
260
+ # == Class Methods ========================================================
261
+
262
+ def self.params
263
+ CONFIG_PARAMS
129
264
  end
130
-
131
- # Returns the HTTP scheme used to make API calls
132
- def scheme
133
- @scheme ||= SCHEME_FOR_SECURE[self.secure?]
265
+
266
+ # == Instance Methods =====================================================
267
+
268
+ def initialize
269
+ credentials = self.rails_credentials
270
+
271
+ CONFIG_PARAMS.each do |param, config|
272
+ value = (
273
+ config[:sources]&.map { |s| credentials[s] }&.compact&.first ||
274
+ config[:env_vars]&.map { |v| ENV[v] }&.compact&.first
275
+ )
276
+
277
+ if (value)
278
+ if (config[:parse])
279
+ instance_variable_set(config[:ivar], config[:parse].call(value))
280
+ else
281
+ instance_variable_set(config[:ivar], value)
282
+ end
283
+ else
284
+ case (config[:default])
285
+ when Proc
286
+ instance_variable_set(config[:ivar], config[:default].call)
287
+ else
288
+ instance_variable_set(config[:ivar], config[:default])
289
+ end
290
+ end
291
+ end
134
292
  end
135
293
 
136
- alias_method :protocol=, :scheme=
137
- alias_method :protocol, :scheme
138
-
139
- # Returns the port used to make API calls
140
- def port
141
- @port ||= (self.secure? ? HTTPS_PORT_DEFAULT : HTTP_PORT_DEFAULT)
294
+ # Returns true if the port used for the API is the default port, otherwise
295
+ # false. 80 for HTTP, 443 for HTTPS.
296
+ def port_default?
297
+ self.port == (self.secure? ? HTTPS_PORT_DEFAULT : HTTP_PORT_DEFAULT)
142
298
  end
143
299
 
144
- # Returns the port used to connect via SOCKS5
145
- def proxy_port
146
- @proxy_port ||= SOCKS5_PORT_DEFAULT
300
+ # Returns true if a proxy is defined, otherwise false.
301
+ def proxy?
302
+ self.proxy_host and self.proxy_host.match(/\A\S+\z/)
147
303
  end
148
-
304
+
149
305
  # Returns the endpoint URL to make API calls
150
306
  def url
151
307
  '%s://%s%s' % [
@@ -159,4 +315,11 @@ class PostageApp::Configuration
159
315
  def http
160
316
  PostageApp::HTTP.connect(self)
161
317
  end
318
+
319
+ protected
320
+ def rails_credentials
321
+ if (PostageApp::Env.rails_with_encrypted_credentials?)
322
+ Rails.application.credentials.postageapp
323
+ end or { }
324
+ end
162
325
  end