postageapp 1.3.0 → 1.4.2

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