howitzer 0.0.1
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.
- data/Gemfile +4 -0
- data/Gemfile.lock +113 -0
- data/LICENSE +22 -0
- data/README.md +44 -0
- data/Rakefile +2 -0
- data/bin/howitzer +41 -0
- data/generators/config/config_generator.rb +24 -0
- data/generators/config/templates/cucumber.yml +11 -0
- data/generators/config/templates/custom.yml +4 -0
- data/generators/config/templates/default.yml +76 -0
- data/generators/cucumber/cucumber_generator.rb +31 -0
- data/generators/cucumber/templates/common_steps.rb +23 -0
- data/generators/cucumber/templates/env.rb +38 -0
- data/generators/cucumber/templates/example.feature +16 -0
- data/generators/cucumber/templates/transformers.rb +28 -0
- data/generators/emails/emails_generator.rb +23 -0
- data/generators/emails/templates/example_email.rb +7 -0
- data/generators/pages/pages_generator.rb +23 -0
- data/generators/pages/templates/example_menu.rb +14 -0
- data/generators/pages/templates/example_page.rb +15 -0
- data/generators/root/root_generator.rb +25 -0
- data/generators/root/templates/.gitignore +20 -0
- data/generators/root/templates/Gemfile +4 -0
- data/generators/root/templates/Rakefile +4 -0
- data/generators/root/templates/boot.rb +16 -0
- data/generators/tasks/tasks_generator.rb +22 -0
- data/generators/tasks/templates/cucumber.rake +57 -0
- data/howitzer.gemspec +36 -0
- data/lib/howitzer.rb +5 -0
- data/lib/howitzer/helpers.rb +58 -0
- data/lib/howitzer/init.rb +5 -0
- data/lib/howitzer/utils.rb +14 -0
- data/lib/howitzer/utils/capybara_patched.rb +22 -0
- data/lib/howitzer/utils/capybara_settings.rb +110 -0
- data/lib/howitzer/utils/data_generator/data_storage.rb +41 -0
- data/lib/howitzer/utils/data_generator/gen.rb +89 -0
- data/lib/howitzer/utils/email/email.rb +46 -0
- data/lib/howitzer/utils/email/mail_client.rb +126 -0
- data/lib/howitzer/utils/email/mailgun.rb +175 -0
- data/lib/howitzer/utils/email/mailgun_helper.rb +35 -0
- data/lib/howitzer/utils/locator_store.rb +118 -0
- data/lib/howitzer/utils/logger.rb +108 -0
- data/lib/howitzer/version.rb +3 -0
- data/lib/howitzer/web_page.rb +62 -0
- metadata +334 -0
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'howitzer/utils/logger'
|
2
|
+
module CapybaraSettings
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def self.base_ff_profile_settings
|
6
|
+
profile = Selenium::WebDriver::Firefox::Profile.new
|
7
|
+
profile["network.http.phishy-userpass-length"] = 255
|
8
|
+
profile["browser.safebrowsing.malware.enabled"] = false
|
9
|
+
profile["network.automatic-ntlm-auth.allow-non-fqdn"] = true
|
10
|
+
profile["network.ntlm.send-lm-response"] = true
|
11
|
+
profile["network.automatic-ntlm-auth.trusted-uris"] = settings.app_main_host
|
12
|
+
profile
|
13
|
+
end
|
14
|
+
|
15
|
+
Capybara.run_server = false
|
16
|
+
Capybara.app_host = ''
|
17
|
+
Capybara.default_wait_time = settings.timeout_small
|
18
|
+
Capybara.ignore_hidden_elements = true
|
19
|
+
case settings.driver.to_sym
|
20
|
+
when :selenium
|
21
|
+
Capybara.register_driver :selenium do |app|
|
22
|
+
params = {browser: settings.sel_browser.to_sym}
|
23
|
+
params[:profile] = base_ff_profile_settings if ff_browser?
|
24
|
+
Capybara::Selenium::Driver.new app, params
|
25
|
+
end
|
26
|
+
when :selenium_dev
|
27
|
+
Capybara.register_driver :selenium_dev do |app|
|
28
|
+
profile = base_ff_profile_settings
|
29
|
+
fb_name = "firebug-#{settings.sel_firebug_version}-fx.xpi"
|
30
|
+
fp_name = "firepath-#{settings.sel_firepath_version}.xpi"
|
31
|
+
[fb_name, fp_name].each do |name|
|
32
|
+
profile.add_extension(File.expand_path(name, File.join('shared', 'vendor', 'firebug')))
|
33
|
+
end
|
34
|
+
profile['extensions.firebug.currentVersion'] = settings.sel_firebug_version # avoid 'first run' tab
|
35
|
+
profile["extensions.firebug.previousPlacement"] = 1
|
36
|
+
profile["extensions.firebug.onByDefault"] = true
|
37
|
+
profile["extensions.firebug.defaultPanelName"] = "firepath"
|
38
|
+
profile["extensions.firebug.script.enableSites"] = true
|
39
|
+
profile["extensions.firebug.net.enableSites"] = true
|
40
|
+
profile["extensions.firebug.console.enableSites"] = true
|
41
|
+
|
42
|
+
Capybara::Selenium::Driver.new app, browser: :firefox, profile: profile
|
43
|
+
end
|
44
|
+
when :webkit
|
45
|
+
require 'capybara-webkit'
|
46
|
+
when :poltergeist
|
47
|
+
require 'capybara/poltergeist'
|
48
|
+
when :sauce
|
49
|
+
caps_opts = {
|
50
|
+
platform: settings.sl_platform,
|
51
|
+
browser_name: settings.sl_browser_name,
|
52
|
+
name: 'Capybara test name should be overwritten',
|
53
|
+
"max-duration" => settings.sl_max_duration,
|
54
|
+
'idle-timeout' => settings.sl_idle_timeout,
|
55
|
+
'selenium-version' => settings.sl_selenium_version,
|
56
|
+
'record-screenshots' => settings.sl_record_screenshot,
|
57
|
+
'video-upload-on-pass' => settings.sl_video_upload_on_pass
|
58
|
+
}
|
59
|
+
|
60
|
+
unless (settings.sl_browser_version.to_s || "").empty?
|
61
|
+
caps_opts['browser-version'] = settings.sl_browser_version.to_s
|
62
|
+
end
|
63
|
+
|
64
|
+
options = {
|
65
|
+
url: settings.sl_url,
|
66
|
+
desired_capabilities: Selenium::WebDriver::Remote::Capabilities.new(caps_opts),
|
67
|
+
http_client: Selenium::WebDriver::Remote::Http::Default.new.tap{|c| c.timeout = settings.timeout_medium},
|
68
|
+
browser: :remote
|
69
|
+
}
|
70
|
+
|
71
|
+
Capybara.register_driver :sauce do |app|
|
72
|
+
Capybara::Selenium::Driver.new(app, options)
|
73
|
+
end
|
74
|
+
|
75
|
+
else
|
76
|
+
log.error "Unknown '#{settings.driver}' driver. Check your settings, it should be one of [selenium, selenium_dev, webkit, sauce]"
|
77
|
+
end
|
78
|
+
|
79
|
+
Capybara.default_driver = settings.driver.to_sym
|
80
|
+
Capybara.javascript_driver = settings.driver.to_sym
|
81
|
+
|
82
|
+
def sauce_resource_path(name)
|
83
|
+
host = "https://#{settings.sl_user}:#{settings.sl_api_key}@saucelabs.com"
|
84
|
+
path = "/rest/#{settings.sl_user}/jobs/#{session_id}/results/#{name}"
|
85
|
+
"#{host}#{path}"
|
86
|
+
end
|
87
|
+
|
88
|
+
def update_sauce_job_status(json_data = {})
|
89
|
+
host = "http://#{settings.sl_user}:#{settings.sl_api_key}@saucelabs.com"
|
90
|
+
path = "/rest/v1/#{settings.sl_user}/jobs/#{session_id}"
|
91
|
+
url = "#{host}#{path}"
|
92
|
+
RestClient.put url, json_data.to_json, content_type: :json, accept: :json
|
93
|
+
end
|
94
|
+
|
95
|
+
def suite_name
|
96
|
+
res = if ENV['RAKE_TASK']
|
97
|
+
res = ENV['RAKE_TASK'].sub(/(?:r?spec|cucumber):?(.*)/, '\1').upcase
|
98
|
+
res.empty? ? 'ALL' : res
|
99
|
+
else
|
100
|
+
'CUSTOM'
|
101
|
+
end
|
102
|
+
"#{res} #{settings.sl_browser_name.upcase}"
|
103
|
+
end
|
104
|
+
|
105
|
+
def session_id
|
106
|
+
Capybara.current_session.driver.browser.instance_variable_get(:@bridge).session_id
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module DataGenerator
|
2
|
+
|
3
|
+
module DataStorage
|
4
|
+
@data ||= {}
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def store(ns, key, value)
|
8
|
+
check_ns(ns)
|
9
|
+
@data[ns][key] = value
|
10
|
+
end
|
11
|
+
|
12
|
+
def extract(ns, key=nil)
|
13
|
+
check_ns(ns)
|
14
|
+
key ? @data[ns][key] : @data[ns]
|
15
|
+
end
|
16
|
+
|
17
|
+
def clear_ns(ns)
|
18
|
+
init_ns(ns)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def check_ns(ns)
|
24
|
+
if ns
|
25
|
+
init_ns(ns) if ns_absent?(ns)
|
26
|
+
else
|
27
|
+
raise 'Data storage namespace can not be empty'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def ns_absent?(ns)
|
32
|
+
!@data.key?(ns)
|
33
|
+
end
|
34
|
+
|
35
|
+
def init_ns(ns)
|
36
|
+
@data[ns] = {}
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module DataGenerator
|
2
|
+
module Gen
|
3
|
+
# examples:
|
4
|
+
# member = Gen::user #user.email: 'u<XXXX>@<settings.mail_pop3_domain>'
|
5
|
+
# member = Gen::user('user@test.com') #user.email: 'member@test.com'
|
6
|
+
# member = Gen::user(settings.def_test_user) #user.email: settings.def_test_user
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def user(params={})
|
10
|
+
prefix = serial
|
11
|
+
default = {
|
12
|
+
email: gen_user_email(prefix),
|
13
|
+
login: nil,
|
14
|
+
first_name: gen_first_name(prefix),
|
15
|
+
last_name: gen_last_name(prefix),
|
16
|
+
password: settings.def_test_pass
|
17
|
+
}
|
18
|
+
User.new(default.merge(params))
|
19
|
+
end
|
20
|
+
|
21
|
+
def given_user_by_number(num)
|
22
|
+
data = DataStorage.extract('user', num.to_i)
|
23
|
+
unless data
|
24
|
+
data = Gen::user
|
25
|
+
DataStorage.store('user', num.to_i, data)
|
26
|
+
end
|
27
|
+
data
|
28
|
+
end
|
29
|
+
|
30
|
+
def serial
|
31
|
+
a = [('a'..'z').to_a, (0..9).to_a].flatten.shuffle
|
32
|
+
"#{Time.now.utc.strftime("%j%H%M%S")}#{a[0..4].join}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def delete_all_mailboxes
|
36
|
+
DataStorage.extract('user').each_value do |user|
|
37
|
+
user.delete_mailbox
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def gen_user_email(serial=nil)
|
44
|
+
"#{gen_user_name(serial)}@#{settings.mail_pop3_domain}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def gen_user_name(serial=nil)
|
48
|
+
gen_entity('u', serial)
|
49
|
+
end
|
50
|
+
|
51
|
+
def gen_first_name(serial=nil)
|
52
|
+
gen_entity('FirstName', serial)
|
53
|
+
end
|
54
|
+
|
55
|
+
def gen_last_name(serial=nil)
|
56
|
+
gen_entity('LastName', serial)
|
57
|
+
end
|
58
|
+
|
59
|
+
def gen_entity(prefix, serial)
|
60
|
+
"#{prefix}#{serial.nil? ? self.serial : serial}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class User < Object
|
65
|
+
|
66
|
+
attr_reader :login, :domain, :email, :password, :mailbox, :first_name, :last_name, :full_name
|
67
|
+
|
68
|
+
def initialize(params={})
|
69
|
+
@email = params.delete(:email)
|
70
|
+
@email_name, @domain = @email.to_s.split('@')
|
71
|
+
@login = params.delete(:login) || @email_name
|
72
|
+
@password = params.delete(:password)
|
73
|
+
@first_name = params.delete(:first_name)
|
74
|
+
@last_name = params.delete(:last_name)
|
75
|
+
@full_name = "#@first_name #@last_name"
|
76
|
+
@mailbox = params.delete(:mailbox)
|
77
|
+
end
|
78
|
+
|
79
|
+
def create_mailbox
|
80
|
+
@mailbox = MailClient.create_mailbox(@email_name) if settings.mail_pop3_domain == @domain
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
def delete_mailbox
|
85
|
+
MailClient.delete_mailbox(@mailbox) unless @mailbox.nil?
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rspec/matchers'
|
2
|
+
require 'howitzer/utils/email/mail_client'
|
3
|
+
|
4
|
+
class Email
|
5
|
+
include RSpec::Matchers
|
6
|
+
attr_reader :recipient_address
|
7
|
+
|
8
|
+
def initialize(message)
|
9
|
+
message.subject.should include(self.class::SUBJECT)
|
10
|
+
@recipient_address = ::Mail::Address.new(message.to.first)
|
11
|
+
@message = message
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.find_by_recipient(recipient)
|
15
|
+
find(recipient, self::SUBJECT)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.find(recipient, subject)
|
19
|
+
messages = MailClient.by_email(recipient).find_mail do |mail|
|
20
|
+
/#{Regexp.escape(subject)}/ === mail.subject && mail.to == [recipient]
|
21
|
+
end
|
22
|
+
|
23
|
+
if messages.first.nil?
|
24
|
+
log.error "#{self} was not found (recipient: '#{recipient}')"
|
25
|
+
messages.first.should_not be_nil
|
26
|
+
end
|
27
|
+
new(messages.first)
|
28
|
+
end
|
29
|
+
|
30
|
+
def plain_text_body
|
31
|
+
get_mime_part(@message, 'text/plain').to_s
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_mime_part(part, type)
|
35
|
+
return part.body if part["content-type"].to_s =~ %r!#{type}!
|
36
|
+
# Recurse the multi-parts
|
37
|
+
part.parts.each do |sub_part|
|
38
|
+
r = get_mime_part(sub_part, type)
|
39
|
+
return r if r
|
40
|
+
end
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
protected :get_mime_part
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'net/pop'
|
2
|
+
require 'timeout'
|
3
|
+
require 'mail'
|
4
|
+
require 'howitzer/utils/email/mailgun_helper'
|
5
|
+
|
6
|
+
class MailClient
|
7
|
+
|
8
|
+
extend MailgunHelper
|
9
|
+
@clients = {}
|
10
|
+
|
11
|
+
def self.default
|
12
|
+
log.info "Connect to default mailbox"
|
13
|
+
options = self.merge_opts
|
14
|
+
@clients[options] = MailClient.send :new unless @clients.has_key?(options)
|
15
|
+
@clients[options]
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.by_email(name)
|
19
|
+
log.info "Connect to '#{name}' mailbox"
|
20
|
+
options = self.merge_opts(:pop3 => {:user_name => name}, :smtp => {})
|
21
|
+
@clients[options] = MailClient.send :new, options unless @clients.has_key?(options)
|
22
|
+
@clients[options]
|
23
|
+
end
|
24
|
+
|
25
|
+
def find_mail(max_wait = settings.mail_pop3_timeout, keep_or_delete = :delete, &block)
|
26
|
+
messages = []
|
27
|
+
time_of_start = Time.now
|
28
|
+
exception = nil
|
29
|
+
while Time.now < time_of_start + max_wait
|
30
|
+
begin
|
31
|
+
exception = nil
|
32
|
+
start do |pop_obj|
|
33
|
+
pop_obj.mails.each do |mail|
|
34
|
+
begin
|
35
|
+
mail_message = Mail.new(mail.pop.to_s)
|
36
|
+
if block_given?
|
37
|
+
if block.call(mail_message)
|
38
|
+
if keep_or_delete == :delete
|
39
|
+
mail.delete
|
40
|
+
log.info "Mail '#{mail_message.subject}' deleted from #{@options[:pop3][:user_name]}"
|
41
|
+
end
|
42
|
+
messages << mail_message
|
43
|
+
@old_ids << mail.unique_id
|
44
|
+
return messages
|
45
|
+
end
|
46
|
+
else
|
47
|
+
messages << mail_message
|
48
|
+
end
|
49
|
+
rescue Net::POPError => e
|
50
|
+
log.warn "Exception in POP find_mail: #{e.message}"
|
51
|
+
exception = e
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
sleep 5
|
57
|
+
rescue Errno::ETIMEDOUT, Errno::ECONNRESET, Net::POPAuthenticationError => e
|
58
|
+
log.warn "Exception in POP find_mail: #{e.message}. Will retry."
|
59
|
+
exception = e
|
60
|
+
end
|
61
|
+
end
|
62
|
+
log.error exception unless exception.nil?
|
63
|
+
messages
|
64
|
+
end
|
65
|
+
|
66
|
+
def send_mail(mail_from, mail_to, mail_subject, mail_body, mail_attachment=nil)
|
67
|
+
log.info "Send emails with next parameters:\n " +
|
68
|
+
"mail_from : #{mail_from},\n mail_to : #{mail_to}\n " +
|
69
|
+
"mail_subject : #{mail_subject},\n has_attachment? : #{!mail_attachment.nil?}"
|
70
|
+
outbound_mail = Mail.new do
|
71
|
+
from(mail_from)
|
72
|
+
subject(mail_subject)
|
73
|
+
body(mail_body)
|
74
|
+
add_file(mail_attachment) unless mail_attachment.nil?
|
75
|
+
end
|
76
|
+
outbound_mail[:to] = mail_to
|
77
|
+
|
78
|
+
retryable(:on => [Errno::ETIMEDOUT, Timeout::Error], :sleep => 10, :tries => 3, :trace => true, :logger => log) do
|
79
|
+
outbound_mail.deliver!
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def empty_inbox
|
84
|
+
begin
|
85
|
+
start {|pop_obj| pop_obj.delete_all }
|
86
|
+
log.info "Email box (#{@options[:pop3][:user_name]}) was purged"
|
87
|
+
rescue Net::POPError => e
|
88
|
+
log.warn "Exception during deletion all messages from '#{@options[:pop3][:user_name]}':\n#{e.message}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def start(&block)
|
93
|
+
retryable(:timeout => settings.mail_pop3_timeout, :sleep => 5, :trace => true, :logger => log) do
|
94
|
+
Net::POP3.start(@options[:pop3][:address], @options[:pop3][:port],
|
95
|
+
@options[:pop3][:user_name], @options[:pop3][:password], &block)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
def initialize(*arg)
|
101
|
+
@options = MailClient.merge_opts(*arg)
|
102
|
+
Net::POP3.enable_ssl(OpenSSL::SSL::VERIFY_NONE)
|
103
|
+
@old_ids = []
|
104
|
+
options = @options
|
105
|
+
::Mail.defaults{delivery_method :smtp, options[:smtp]}
|
106
|
+
@time_of_start = Time.now
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.merge_opts(opts={:smtp => {}, :pop3 => {}})
|
110
|
+
def_smtp_opts = {:address => settings.mail_smtp_server,
|
111
|
+
:port => settings.mail_smtp_port,
|
112
|
+
:domain => settings.mail_smtp_domain,
|
113
|
+
:user_name => settings.mail_smtp_user_name,
|
114
|
+
:password => settings.mail_smtp_user_pass,
|
115
|
+
:authentication => 'plain',
|
116
|
+
:enable_starttls_auto => true}
|
117
|
+
|
118
|
+
def_pop3_opts = {:address => settings.mail_pop3_server,
|
119
|
+
:port => settings.mail_pop3_port,
|
120
|
+
:user_name => settings.mail_pop3_user_name,
|
121
|
+
:password => settings.mail_pop3_user_pass}
|
122
|
+
{:smtp => def_smtp_opts.merge(opts[:smtp]),
|
123
|
+
:pop3 => def_pop3_opts.merge(opts[:pop3])}
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
#For details, please see https://github.com/mailgun/mailgun.rb or https://github.com/hardikshah/mailgun.rb
|
2
|
+
#Modified from original adding rest-client 'remove'
|
3
|
+
require 'active_resource'
|
4
|
+
require 'rest-client'
|
5
|
+
|
6
|
+
class Mailgun
|
7
|
+
|
8
|
+
# Initializes Mailgun API
|
9
|
+
# Must be called before any other method
|
10
|
+
#
|
11
|
+
# Parameters:
|
12
|
+
# api_key - Your API key
|
13
|
+
# api_url - API base URL
|
14
|
+
#
|
15
|
+
def self.init(api_key, api_url = "https://mailgun.net/api/")
|
16
|
+
MailgunResource.password = api_key
|
17
|
+
api_url = api_url.gsub(/\/$/, '') + "/"
|
18
|
+
MailgunResource.site = api_url
|
19
|
+
end
|
20
|
+
|
21
|
+
# This is a patch of private ActiveResource method.
|
22
|
+
# It takes HTTPResponse and raise AR-like error if response code is not 2xx
|
23
|
+
def self.handle_response(response)
|
24
|
+
case response.code.to_i
|
25
|
+
when 301,302
|
26
|
+
raise(Redirection.new(response))
|
27
|
+
when 200...400
|
28
|
+
response
|
29
|
+
when 400
|
30
|
+
raise(ActiveResource::BadRequest.new(response))
|
31
|
+
when 401
|
32
|
+
raise(ActiveResource::UnauthorizedAccess.new(response))
|
33
|
+
when 403
|
34
|
+
raise(ActiveResource::ForbiddenAccess.new(response))
|
35
|
+
when 404
|
36
|
+
raise(ActiveResource::ResourceNotFound.new(response))
|
37
|
+
when 405
|
38
|
+
raise(ActiveResource::MethodNotAllowed.new(response))
|
39
|
+
when 409
|
40
|
+
raise(ActiveResource::ResourceConflict.new(response))
|
41
|
+
when 410
|
42
|
+
raise(ActiveResource::ResourceGone.new(response))
|
43
|
+
when 422
|
44
|
+
raise(ActiveResource::ResourceInvalid.new(response))
|
45
|
+
when 401...500
|
46
|
+
raise(ActiveResource::ClientError.new(response))
|
47
|
+
when 500...600
|
48
|
+
raise(ActiveResource::ServerError.new(response))
|
49
|
+
else
|
50
|
+
raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module RequestBuilder
|
55
|
+
def prepare_request(url_string)
|
56
|
+
uri = URI.parse(url_string)
|
57
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
58
|
+
http.use_ssl = true if uri.port == 443
|
59
|
+
return [http, (uri.path + '?' + uri.query)]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class MailgunMessage
|
65
|
+
extend Mailgun::RequestBuilder
|
66
|
+
|
67
|
+
MAILGUN_TAG = 'X-Mailgun-Tag'
|
68
|
+
|
69
|
+
# Sends a MIME-formatted message
|
70
|
+
#
|
71
|
+
# raw_mime =
|
72
|
+
# "Content-Type: text/plain;charset=utf-8\n" +
|
73
|
+
# "From: me@host\n" +
|
74
|
+
# "To: you@host\n" +
|
75
|
+
# "Subject: Hello!\n\n" +
|
76
|
+
# "Body"
|
77
|
+
# MailgunMessage::send_raw("me@host", "you@host", raw_mime)
|
78
|
+
#
|
79
|
+
def self.send_raw(sender, recipients, raw_body, servername='')
|
80
|
+
uri_str = "#{MailgunResource.site}messages.eml?api_key=#{MailgunResource.password}&servername=#{servername}"
|
81
|
+
http, url = prepare_request(uri_str)
|
82
|
+
data = "#{sender}\n#{recipients}\n\n#{raw_body}"
|
83
|
+
res = http.post(url, data, {"Content-type" => "text/plain" })
|
84
|
+
Mailgun::handle_response(res)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Sends a plain-text message
|
88
|
+
#
|
89
|
+
# MailgunMessage::send_text("me@host.com",
|
90
|
+
# "you@host.com",
|
91
|
+
# "Subject",
|
92
|
+
# "Hi!\nThis is message body")
|
93
|
+
#
|
94
|
+
def self.send_text(sender, recipients, subject, text, servername='', options = nil)
|
95
|
+
uri_str = "#{MailgunResource.site}messages.txt?api_key=#{MailgunResource.password}&servername=#{servername}"
|
96
|
+
params = { :sender => sender, :recipients => recipients, :subject => subject, :body => text}
|
97
|
+
unless options.nil?
|
98
|
+
params['options'] = ActiveSupport::JSON.encode(options)
|
99
|
+
end
|
100
|
+
http, url = prepare_request(uri_str)
|
101
|
+
req = Net::HTTP::Post.new(url)
|
102
|
+
req.set_form_data(params)
|
103
|
+
res = http.request(req)
|
104
|
+
Mailgun::handle_response(res)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Base class for Mailgun resource classes
|
109
|
+
# It adds upsert() method on top of ActiveResource::Base
|
110
|
+
#
|
111
|
+
class MailgunResource < ActiveResource::Base
|
112
|
+
self.user = "api_key"
|
113
|
+
extend Mailgun::RequestBuilder
|
114
|
+
|
115
|
+
# Create new resource or update it if resource already exist.
|
116
|
+
# There are 2 differences between upsert() and save():
|
117
|
+
# - Upsert does not raise an exception if a resource already exist.
|
118
|
+
# - Upsert does not return the id of freshly inserted resource
|
119
|
+
#
|
120
|
+
# >> route = Route.new
|
121
|
+
# >> route.pattern = '*@mydomain.com'
|
122
|
+
# >> route.destination = 'http://mydomain.com/addcomment'
|
123
|
+
# >> route.upsert()
|
124
|
+
#
|
125
|
+
def upsert()
|
126
|
+
self.class.post('upsert', {}, self.to_xml())
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# All mail arriving to emails addresses that have mailboxes associated
|
131
|
+
# will be stored on the server and can be later accessed via IMAP or POP3
|
132
|
+
# protocols.
|
133
|
+
#
|
134
|
+
# Mailbox has several properties:
|
135
|
+
#
|
136
|
+
# alex@gmail.com
|
137
|
+
# ^ ^
|
138
|
+
# | |
|
139
|
+
# user domain
|
140
|
+
#
|
141
|
+
class Mailbox < MailgunResource
|
142
|
+
# Example of a CSV file:
|
143
|
+
#
|
144
|
+
# john@domain.com, password
|
145
|
+
# doe@domain.com, password2
|
146
|
+
#
|
147
|
+
def self.upsert_from_csv(mailboxes)
|
148
|
+
uri_str = "#{MailgunResource.site}mailboxes.txt?api_key=#{MailgunResource.password}"
|
149
|
+
http, url = prepare_request(uri_str)
|
150
|
+
res = http.post(url, mailboxes, {"Content-type" => "text/plain" })
|
151
|
+
Mailgun::handle_response(res)
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.remove(mailbox)
|
155
|
+
res = RestClient.delete "https://api:#{MailgunResource.password}"\
|
156
|
+
"@api.mailgun.net/v2/#{mailbox.domain}/mailboxes/#{mailbox.user}"
|
157
|
+
Mailgun::handle_response(res)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
# This class represents Mailgun route.
|
163
|
+
# A route has 2 properties: pattern and destination
|
164
|
+
#
|
165
|
+
# Examples of patterns:
|
166
|
+
# - '*' - match all
|
167
|
+
# - exact match (foo@bar.com)
|
168
|
+
# - domain pattern, i.e. a pattern like "@example.com" - it will match all emails going to example.com
|
169
|
+
# - any regular expression
|
170
|
+
#
|
171
|
+
# Destination can be one of:
|
172
|
+
# - an emails address to forward to
|
173
|
+
# - a URL for HTTP POST
|
174
|
+
class Route < MailgunResource
|
175
|
+
end
|