ses_machine 0.0.1.dev
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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +41 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +13 -0
- data/Rakefile +23 -0
- data/app/controllers/application_controller.rb +3 -0
- data/app/controllers/ses_machine_controller.rb +64 -0
- data/app/helpers/ses_machine_helper.rb +8 -0
- data/app/views/layouts/ses_machine/application.html.erb +20 -0
- data/app/views/ses_machine/activity.html.erb +37 -0
- data/app/views/ses_machine/index.html.erb +17 -0
- data/app/views/ses_machine/show_message.html.erb +76 -0
- data/generators/ses_machine/USAGE +3 -0
- data/generators/ses_machine/ses_machine_generator.rb +19 -0
- data/generators/ses_machine/templates/README +7 -0
- data/generators/ses_machine/templates/ses_machine.css +14 -0
- data/generators/ses_machine/templates/ses_machine.rb +19 -0
- data/generators/ses_machine/templates/ses_machine.yml +15 -0
- data/generators/ses_machine/templates/ses_machine_hooks.rb +33 -0
- data/init.rb +3 -0
- data/install.rb +3 -0
- data/lib/ses_machine/bounce.rb +16 -0
- data/lib/ses_machine/config.rb +107 -0
- data/lib/ses_machine/db.rb +92 -0
- data/lib/ses_machine/errors.rb +24 -0
- data/lib/ses_machine/mailer.rb +68 -0
- data/lib/ses_machine/routing.rb +8 -0
- data/lib/ses_machine/version.rb +13 -0
- data/lib/ses_machine.rb +88 -0
- data/lib/tasks/db.rake +28 -0
- data/lib/tasks/ses_machine.rake +91 -0
- data/public/stylesheets/all.css +0 -0
- data/ses_machine.gemspec +29 -0
- data/test/config_test.rb +34 -0
- data/test/db_test.rb +50 -0
- data/test/fixtures/config/ses_machine.yml +11 -0
- data/test/routing_test.rb +17 -0
- data/test/ses_machine_controller_test.rb +49 -0
- data/test/ses_machine_generator_test.rb +38 -0
- data/test/ses_machine_test.rb +10 -0
- data/test/test_helper.rb +7 -0
- data/uninstall.rb +3 -0
- metadata +193 -0
@@ -0,0 +1,92 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
|
4
|
+
module SesMachine #:nodoc:
|
5
|
+
class DB #:nodoc:
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def get_keywords(array)
|
9
|
+
array.map{|w| w.downcase.to_s}.uniq.delete_if{|w| w.size < 3}
|
10
|
+
end
|
11
|
+
|
12
|
+
def update_daily_stats
|
13
|
+
map = <<-JS
|
14
|
+
function() {
|
15
|
+
var self = this;
|
16
|
+
var key = this.date - (this.date % 86400000);
|
17
|
+
var value = {total: 1};
|
18
|
+
|
19
|
+
types.forEach(function(type) {
|
20
|
+
value[type] = self.bounce_type == type ? 1 : 0;
|
21
|
+
});
|
22
|
+
|
23
|
+
emit(key, value);
|
24
|
+
}
|
25
|
+
JS
|
26
|
+
|
27
|
+
reduce = <<-JS
|
28
|
+
function(key, values) {
|
29
|
+
var sum = {};
|
30
|
+
var total = 0;
|
31
|
+
types.forEach(function(type) {
|
32
|
+
sum[type] = 0;
|
33
|
+
});
|
34
|
+
|
35
|
+
values.forEach(function(value) {
|
36
|
+
for(var type in value) {
|
37
|
+
sum[type] += value[type];
|
38
|
+
}
|
39
|
+
});
|
40
|
+
|
41
|
+
types.forEach(function(type) {
|
42
|
+
total += sum[type];
|
43
|
+
});
|
44
|
+
sum['total'] = total;
|
45
|
+
|
46
|
+
return sum;
|
47
|
+
}
|
48
|
+
JS
|
49
|
+
t = SesMachine.database['mails']
|
50
|
+
t.map_reduce(map, reduce, :out => {'merge' => 'daily_stats'}, :raw => true, :scope => {'types' => SesMachine::Bounce::TYPES.values})
|
51
|
+
end
|
52
|
+
|
53
|
+
def update_monthly_stats
|
54
|
+
map = <<-JS
|
55
|
+
function() {
|
56
|
+
|
57
|
+
var date = new Date(this['_id'])
|
58
|
+
|
59
|
+
var key = {
|
60
|
+
year: date.getFullYear(),
|
61
|
+
month: date.getMonth() + 1
|
62
|
+
};
|
63
|
+
var value = this.value;
|
64
|
+
|
65
|
+
emit(key, value);
|
66
|
+
}
|
67
|
+
JS
|
68
|
+
|
69
|
+
reduce = <<-JS
|
70
|
+
function(key, values) {
|
71
|
+
var sum = {total: 0};
|
72
|
+
|
73
|
+
types.forEach(function(type) {
|
74
|
+
sum[type] = 0;
|
75
|
+
});
|
76
|
+
|
77
|
+
values.forEach(function(value) {
|
78
|
+
for(var type in value) {
|
79
|
+
sum[type] += value[type];
|
80
|
+
}
|
81
|
+
});
|
82
|
+
|
83
|
+
return sum;
|
84
|
+
}
|
85
|
+
JS
|
86
|
+
t = SesMachine.database['daily_stats']
|
87
|
+
t.map_reduce(map, reduce, :out => {'merge' => 'monthly_stats'}, :raw => true, :scope => {'types' => SesMachine::Bounce::TYPES.values})
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
|
4
|
+
module SesMachine #:nodoc
|
5
|
+
module Errors #:nodoc
|
6
|
+
|
7
|
+
# Raised when the database connection has not been set up properly, either
|
8
|
+
# by attempting to set an object on the db that is not a +Mongo::DB+, or
|
9
|
+
# not setting anything at all.
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
#
|
13
|
+
# <tt>InvalidDatabase.new("Not a DB")</tt>
|
14
|
+
class InvalidDatabase < StandardError
|
15
|
+
def initialize(database)
|
16
|
+
@database = database
|
17
|
+
end
|
18
|
+
|
19
|
+
def message
|
20
|
+
"Database should be a Mongo::DB, not #{@database.class.name}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
|
4
|
+
module SesMachine #:nodoc:
|
5
|
+
class Mailer #:nodoc:
|
6
|
+
|
7
|
+
def initialize(*args); end
|
8
|
+
|
9
|
+
def perform_delivery_ses_machine(mail)
|
10
|
+
raw_source = SesMachine.use_dkim? ? sign_mail(mail) : mail.encoded
|
11
|
+
|
12
|
+
begin
|
13
|
+
response = SesMachine.ses.send_raw_email(raw_source, :source => SesMachine.email_account)
|
14
|
+
rescue AWS::SES::ResponseError => e
|
15
|
+
response = e.response
|
16
|
+
end
|
17
|
+
|
18
|
+
doc = {
|
19
|
+
:address => mail.to,
|
20
|
+
:subject => mail.subject,
|
21
|
+
:raw_source => raw_source,
|
22
|
+
:date => response.headers['date'].to_time.utc,
|
23
|
+
:request_id => response.request_id,
|
24
|
+
:response => response.to_s,
|
25
|
+
:response_code => response.code,
|
26
|
+
:response_error => response.error.to_s,
|
27
|
+
:bounce_type => response.error? ? SesMachine::Bounce::TYPES[:unknown] : 0,
|
28
|
+
'_keywords' => SesMachine::DB.get_keywords([mail.to].flatten + mail.subject.split)
|
29
|
+
}
|
30
|
+
|
31
|
+
doc.merge!(:message_id => response.message_id) unless response.error?
|
32
|
+
|
33
|
+
SesMachine.database['mails'].insert(doc)
|
34
|
+
end
|
35
|
+
|
36
|
+
alias_method :deliver!, :perform_delivery_ses_machine
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def dkim_signer
|
41
|
+
key = File.readlines(SesMachine.dkim_private_key).join
|
42
|
+
DKIM::Signer.new(SesMachine.dkim_domain, SesMachine.dkim_selector, key)
|
43
|
+
end
|
44
|
+
|
45
|
+
def sign_mail(mail)
|
46
|
+
# Read more: http://docs.amazonwebservices.com/ses/latest/DeveloperGuide/DKIM.html
|
47
|
+
exclude_headers = ['Message-Id', 'Date', 'Return-Path', 'Bounces-To']
|
48
|
+
exclude_headers_rxp = /^(#{exclude_headers.join('|')}):/i
|
49
|
+
signer = dkim_signer
|
50
|
+
raw_source = ''
|
51
|
+
mail.encoded.each_line do |line|
|
52
|
+
unless line =~ exclude_headers_rxp
|
53
|
+
unless line.blank?
|
54
|
+
line.gsub!(/\n/, "\r\n")
|
55
|
+
line.gsub!(/\r+/, "\r")
|
56
|
+
end
|
57
|
+
signer.feed(line)
|
58
|
+
raw_source << line
|
59
|
+
end
|
60
|
+
end
|
61
|
+
signature = signer.finish
|
62
|
+
"#{signature.signature_header}\r\n#{raw_source}"
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
ActionMailer::Base.add_delivery_method :ses_machine, SesMachine::Mailer
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
|
4
|
+
Rails.application.routes.draw do
|
5
|
+
match "ses_machine" => "ses_machine#index", :as => :ses_machine
|
6
|
+
match "ses_machine/activity" => "ses_machine#activity", :as => :ses_machine_activity
|
7
|
+
match "ses_machine/message/:id" => "ses_machine#show_message", :as => :ses_machine_message
|
8
|
+
end
|
data/lib/ses_machine.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
|
4
|
+
lib = File.expand_path(File.dirname(__FILE__))
|
5
|
+
$:.unshift lib unless $:.include?(lib)
|
6
|
+
|
7
|
+
# require 'rubygems'
|
8
|
+
|
9
|
+
# gem 'rails', '~> 2.3.8'
|
10
|
+
# gem 'activesupport', '~> 2.3.8'
|
11
|
+
# gem 'actionmailer', '~> 2.3.8'
|
12
|
+
# gem 'will_paginate', '~> 2.3'
|
13
|
+
|
14
|
+
require 'singleton'
|
15
|
+
require 'mongo'
|
16
|
+
require 'action_mailer'
|
17
|
+
require 'action_mailer/version'
|
18
|
+
require 'dkim'
|
19
|
+
require 'mail'
|
20
|
+
require 'aws/ses'
|
21
|
+
require 'will_paginate'
|
22
|
+
require 'ses_machine/bounce'
|
23
|
+
require 'ses_machine/config'
|
24
|
+
require 'ses_machine/db'
|
25
|
+
require 'ses_machine/errors'
|
26
|
+
require 'ses_machine/mailer'
|
27
|
+
require 'ses_machine/routing'
|
28
|
+
|
29
|
+
%w{ models controllers helpers}.each do |dir|
|
30
|
+
path = File.join(File.dirname(__FILE__), '..', 'app', dir)
|
31
|
+
$LOAD_PATH << path
|
32
|
+
# ActiveSupport::Dependencies.load_paths << path
|
33
|
+
# ActiveSupport::Dependencies.load_once_paths.delete(path)
|
34
|
+
# ActionController::Base.append_view_path(File.join(File.dirname(__FILE__), '..', 'app', 'views'))
|
35
|
+
end
|
36
|
+
|
37
|
+
# WillPaginate.enable_actionpack
|
38
|
+
if defined?(::Rails::Railtie)
|
39
|
+
require 'will_paginate/railtie'
|
40
|
+
end
|
41
|
+
|
42
|
+
module SesMachine #:nodoc
|
43
|
+
|
44
|
+
module Hooks; end
|
45
|
+
|
46
|
+
class << self
|
47
|
+
|
48
|
+
# Sets the SesMachine configuration options. Best used by passing a block.
|
49
|
+
#
|
50
|
+
# Example:
|
51
|
+
#
|
52
|
+
# SesMachine.configure do |config|
|
53
|
+
# config.use_dkim = true
|
54
|
+
# config.dkim_domain = 'example.com'
|
55
|
+
# config.dkim_selector = 'ses'
|
56
|
+
# config.dkim_private_key = '/path/to/private/key'
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# Returns:
|
60
|
+
#
|
61
|
+
# The SesMachine +Config+ singleton instance.
|
62
|
+
def configure
|
63
|
+
SesMachine::Config.instance
|
64
|
+
config = SesMachine::Config.instance
|
65
|
+
block_given? ? yield(config) : config
|
66
|
+
end
|
67
|
+
alias :config :configure
|
68
|
+
end
|
69
|
+
|
70
|
+
# Take all the public instance methods from the Config singleton and allow
|
71
|
+
# them to be accessed through the SesMachine module directly.
|
72
|
+
#
|
73
|
+
# Example:
|
74
|
+
#
|
75
|
+
# <tt>SesMachine.database = Mongo::Connection.new.db("test")</tt>
|
76
|
+
SesMachine::Config.public_instance_methods(false).each do |name|
|
77
|
+
(class << self; self; end).class_eval <<-EOT
|
78
|
+
def #{name}(*args)
|
79
|
+
configure.send("#{name}", *args)
|
80
|
+
end
|
81
|
+
EOT
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
ENV['RAILS_ENV'] ||= Rails.env || 'development'
|
86
|
+
ENV['RAILS_ROOT'] ||= Rails.root.to_s || File.dirname(__FILE__) + '/../../../..'
|
87
|
+
|
88
|
+
SesMachine.config.load
|
data/lib/tasks/db.rake
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
namespace :ses_machine do
|
4
|
+
namespace :db do
|
5
|
+
|
6
|
+
desc 'Update daily statistics'
|
7
|
+
task :update_daily_stats => :environment do
|
8
|
+
response = SesMachine::DB.update_daily_stats
|
9
|
+
puts "DAILY STATS (#{response['timeMillis']}ms) - #{response['ok'] ? 'Success' : 'Failure'}"
|
10
|
+
end
|
11
|
+
|
12
|
+
desc 'Update monthly statistics'
|
13
|
+
task :update_monthly_stats => :environment do
|
14
|
+
response = SesMachine::DB.update_monthly_stats
|
15
|
+
puts "MONTHLY STATS (#{response['timeMillis']}ms) - #{response['ok'] ? 'Success' : 'Failure'}"
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'Create database indices'
|
19
|
+
task :create_indices => :environment do
|
20
|
+
t = SesMachine.database['mails']
|
21
|
+
t.ensure_index([['message_id', Mongo::ASCENDING]])
|
22
|
+
t.ensure_index([['bounce_type', Mongo::ASCENDING]])
|
23
|
+
t.ensure_index([['_keywords', Mongo::ASCENDING]])
|
24
|
+
t.ensure_index([['date', Mongo::DESCENDING]])
|
25
|
+
t.ensure_index([['address', Mongo::ASCENDING]])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'net/imap'
|
4
|
+
|
5
|
+
namespace :ses_machine do
|
6
|
+
desc 'Check email with IMAP for new bounced and spam messages'
|
7
|
+
task :check_email_imap => :environment do
|
8
|
+
|
9
|
+
imap = Net::IMAP.new(SesMachine.email_server, SesMachine.email_port, SesMachine.email_use_ssl)
|
10
|
+
imap.login(SesMachine.email_account, SesMachine.email_password)
|
11
|
+
t = SesMachine.database['mails']
|
12
|
+
|
13
|
+
folders = SesMachine::Bounce::TYPES.keys.map {|i| i.to_s.classify}
|
14
|
+
folders.delete('EmailSent')
|
15
|
+
folders << 'MessageNotFound'
|
16
|
+
folders.each do |folder|
|
17
|
+
imap.create("SesMachine/#{folder}") unless imap.list('SesMachine/', folder)
|
18
|
+
end
|
19
|
+
|
20
|
+
SesMachine.email_imap_folders.each do |folder|
|
21
|
+
imap.select(folder)
|
22
|
+
imap.search(['FROM', 'email-bounces.amazonses.com', 'NOT', 'SEEN']).each do |message|
|
23
|
+
data = imap.fetch(message, ['UID', 'ENVELOPE', 'RFC822'])[0]
|
24
|
+
mail = Mail.read_from_string(data.attr['RFC822'])
|
25
|
+
message_id = data.attr['ENVELOPE']['in_reply_to']
|
26
|
+
if message_id.blank?
|
27
|
+
message_id = mail['X-Original-To'].to_s.split('@').first.to_s
|
28
|
+
else
|
29
|
+
message_id = message_id.split('@').first[1..-1].to_s
|
30
|
+
end
|
31
|
+
doc = t.find_one('message_id' => message_id)
|
32
|
+
if doc
|
33
|
+
email = doc['address'].first
|
34
|
+
bounce = {
|
35
|
+
:uid => data.attr['UID'],
|
36
|
+
:message_id => data.attr['ENVELOPE']['message_id'].split('@').first[1..-1],
|
37
|
+
:raw_source => data.attr['RFC822'],
|
38
|
+
:date => data.attr['ENVELOPE']['date'].to_time.utc
|
39
|
+
}
|
40
|
+
if mail['auto-submitted'].to_s == 'auto-replied'
|
41
|
+
imap.copy(message, 'SesMachine/AutoResponder')
|
42
|
+
bounce_type = SesMachine::Bounce::TYPES[:auto_responder]
|
43
|
+
SesMachine::Hooks.auto_responder_hook(email) if SesMachine::Hooks.respond_to?(:auto_responder_hook)
|
44
|
+
elsif mail.bounced?
|
45
|
+
if mail.retryable?
|
46
|
+
imap.copy(message, 'SesMachine/SoftBounce')
|
47
|
+
bounce_type = SesMachine::Bounce::TYPES[:soft_bounce]
|
48
|
+
SesMachine::Hooks.soft_bounce_hook(email) if SesMachine::Hooks.respond_to?(:soft_bounce_hook)
|
49
|
+
else
|
50
|
+
imap.copy(message, 'SesMachine/HardBounce')
|
51
|
+
bounce_type = SesMachine::Bounce::TYPES[:hard_bounce]
|
52
|
+
SesMachine::Hooks.hard_bounce_hook(email) if SesMachine::Hooks.respond_to?(:hard_bounce_hook)
|
53
|
+
end
|
54
|
+
bounce.merge!(:details => mail.diagnostic_code.to_s)
|
55
|
+
else
|
56
|
+
imap.copy(message, 'SesMachine/Unknown')
|
57
|
+
bounce_type = SesMachine::Bounce::TYPES[:unknown]
|
58
|
+
SesMachine::Hooks.unknown_hook(email) if SesMachine::Hooks.respond_to?(:unknown_hook)
|
59
|
+
end
|
60
|
+
t.update({'_id' => doc['_id']}, {'$set' => {'bounce' => bounce, 'bounce_type' => bounce_type}})
|
61
|
+
else
|
62
|
+
imap.copy(message, 'SesMachine/MessageNotFound')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
SesMachine.email_imap_folders.each do |folder|
|
67
|
+
imap.select(folder)
|
68
|
+
imap.search(['FROM', 'complaints@email-abuse.amazonses.com', 'NOT', 'SEEN']).each do |message|
|
69
|
+
data = imap.fetch(message, ['UID', 'ENVELOPE', 'RFC822'])[0]
|
70
|
+
message_id = data.attr['RFC822'].split("\r\n").grep(/\AMessage-ID: (.+)\z/ix)[1][/<(.+)@/, 1].to_s
|
71
|
+
doc = t.find_one('message_id' => message_id)
|
72
|
+
if doc
|
73
|
+
email = doc['address'].first
|
74
|
+
bounce = {
|
75
|
+
:uid => data.attr['UID'],
|
76
|
+
:message_id => data.attr['ENVELOPE']['message_id'].split('@').first[1..-1],
|
77
|
+
:raw_source => data.attr['RFC822'],
|
78
|
+
:date => data.attr['ENVELOPE']['date'].to_time.utc
|
79
|
+
}
|
80
|
+
imap.copy(message, 'SesMachine/SpamComplaint')
|
81
|
+
SesMachine::Hooks.spam_complaint_hook(email) if SesMachine::Hooks.respond_to?(:spam_complaint_hook)
|
82
|
+
t.update({'_id' => doc['_id']}, {'$set' => {'bounce' => bounce, 'bounce_type' => SesMachine::Bounce::TYPES[:spam_complaint]}})
|
83
|
+
else
|
84
|
+
imap.copy(message, 'SesMachine/MessageNotFound')
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
imap.logout()
|
89
|
+
imap.disconnect()
|
90
|
+
end
|
91
|
+
end
|
File without changes
|
data/ses_machine.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require File.expand_path('../lib/ses_machine/version', __FILE__)
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'ses_machine'
|
7
|
+
s.version = SesMachine::VERSION::STRING
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ['Kirill Nikitin']
|
10
|
+
s.email = ['locke23rus@gmail.com']
|
11
|
+
s.homepage = 'http://rubygems.org/gems/ses_machine'
|
12
|
+
s.summary = 'SES Machine'
|
13
|
+
s.description = 'SES Machine description'
|
14
|
+
s.license = 'MIT'
|
15
|
+
|
16
|
+
s.required_rubygems_version = '>= 1.3.6'
|
17
|
+
|
18
|
+
s.add_dependency 'mail', '>= 2.4.4'
|
19
|
+
s.add_dependency 'aws-ses', '>= 0.4.4'
|
20
|
+
s.add_dependency 'rubydkim', '>= 0.3.1'
|
21
|
+
s.add_dependency 'mongo', '>= 1.9.1'
|
22
|
+
s.add_dependency 'bson', '>= 1.9.1'
|
23
|
+
s.add_dependency 'bson_ext', '>= 1.9.1'
|
24
|
+
s.add_dependency 'will_paginate', '>= 3.0.4'
|
25
|
+
|
26
|
+
s.files = `git ls-files`.split("\n")
|
27
|
+
s.test_files = `git ls-files -- test/*`.split("\n")
|
28
|
+
s.require_path = 'lib'
|
29
|
+
end
|
data/test/config_test.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class ConfigTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def setup
|
8
|
+
@config = SesMachine::Config.instance
|
9
|
+
@config.send(:remove_instance_variable, :@database) if @config.instance_variable_defined?(:@database)
|
10
|
+
end
|
11
|
+
|
12
|
+
def teardown
|
13
|
+
@config.send(:remove_instance_variable, :@database) if @config.instance_variable_defined?(:@database)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_load_config
|
17
|
+
assert_nil @config.database
|
18
|
+
filename = File.join(File.dirname(__FILE__), 'fixtures', 'config', 'ses_machine.yml')
|
19
|
+
@config.load(filename)
|
20
|
+
assert_not_nil @config.database
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_set_database_with_invalid_database
|
24
|
+
assert_raise(SesMachine::Errors::InvalidDatabase) { @config.database = 'test' }
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_load_config_from_hash
|
28
|
+
filename = File.join(File.dirname(__FILE__), 'fixtures', 'config', 'ses_machine.yml')
|
29
|
+
settings = YAML.load_file(filename)['test']
|
30
|
+
assert_nil @config.database
|
31
|
+
@config.from_hash(settings)
|
32
|
+
assert_equal 'ses_machine_config_test', @config.database.name
|
33
|
+
end
|
34
|
+
end
|
data/test/db_test.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class DBTest < ActiveSupport::TestCase
|
6
|
+
|
7
|
+
def setup
|
8
|
+
SesMachine.config.load(File.join(File.dirname(__FILE__), 'fixtures', 'config', 'ses_machine.yml'))
|
9
|
+
SesMachine.database['mails'].remove
|
10
|
+
SesMachine.database['daily_stats'].remove
|
11
|
+
SesMachine.database['monthly_stats'].remove
|
12
|
+
end
|
13
|
+
|
14
|
+
def teardown
|
15
|
+
SesMachine.database['mails'].remove
|
16
|
+
SesMachine.database['daily_stats'].remove
|
17
|
+
SesMachine.database['monthly_stats'].remove
|
18
|
+
end
|
19
|
+
|
20
|
+
# def test_get_keywords
|
21
|
+
# words = ['Q', 'qw', 'QWE', 'Й', 'йц', 'ЙЦУ', '1', '12', '123']
|
22
|
+
# assert_equal ['qwe', 'йцу', '123'], SesMachine::DB.get_keywords(words)
|
23
|
+
# end
|
24
|
+
|
25
|
+
def test_update_daily_stats
|
26
|
+
load_mails
|
27
|
+
response = SesMachine::DB.update_daily_stats
|
28
|
+
assert_equal 3, SesMachine.database['mails'].count
|
29
|
+
assert_equal 2, SesMachine.database['daily_stats'].count
|
30
|
+
assert_equal 1, response['ok']
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_update_monthly_stats
|
34
|
+
load_mails
|
35
|
+
SesMachine::DB.update_daily_stats
|
36
|
+
assert_equal 0, SesMachine.database['monthly_stats'].count
|
37
|
+
response = SesMachine::DB.update_monthly_stats
|
38
|
+
assert_equal 2, SesMachine.database['monthly_stats'].count
|
39
|
+
assert_equal 1, response['ok']
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def load_mails
|
45
|
+
SesMachine.database['mails'].insert('bounce_type' => SesMachine::Bounce::TYPES[:mail_sent], 'date' => 2.month.ago.to_time.utc)
|
46
|
+
SesMachine.database['mails'].insert('bounce_type' => SesMachine::Bounce::TYPES[:unknown], 'date' => Time.now.utc)
|
47
|
+
SesMachine.database['mails'].insert('bounce_type' => SesMachine::Bounce::TYPES[:hard_bounce], 'date' => Time.now.utc)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class RoutingTest < ActionController::TestCase
|
6
|
+
def setup
|
7
|
+
ActionController::Routing::Routes.draw do |map|
|
8
|
+
map.ses_machine
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
test 'map index ses machine' do
|
13
|
+
assert_recognizes({:controller => 'ses_machine', :action => 'index'}, {:path => 'ses_machine', :method => :get})
|
14
|
+
assert_recognizes({:controller => 'ses_machine', :action => 'activity'}, {:path => 'ses_machine/activity', :method => :get})
|
15
|
+
assert_recognizes({:controller => 'ses_machine', :action => 'show_message', :id => '1'}, {:path => 'ses_machine/message/1', :method => :get})
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class SesMachineControllerTest < ActionController::TestCase
|
6
|
+
def setup
|
7
|
+
SesMachine.config.load(File.join(File.dirname(__FILE__), 'fixtures', 'config', 'ses_machine.yml'))
|
8
|
+
SesMachine.database['mails'].remove
|
9
|
+
SesMachine.database['mails'].insert('_id' => BSON::ObjectId('4d95f4ebf023ca49fa000001'),
|
10
|
+
'date' => Time.now.utc,
|
11
|
+
'address' => ['test@example.com'],
|
12
|
+
'subject' => 'Test mail',
|
13
|
+
'raw_source' => 'Test')
|
14
|
+
ActionController::Routing::Routes.draw { |map| map.ses_machine }
|
15
|
+
@controller = SesMachineController.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def teardown
|
19
|
+
SesMachine.database['mails'].remove
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_index
|
23
|
+
get :index
|
24
|
+
assert_response :success
|
25
|
+
assert_not_nil assigns(:count_mails_sent)
|
26
|
+
assert_not_nil assigns(:count_mails_bounced)
|
27
|
+
assert_not_nil assigns(:monthly_stats)
|
28
|
+
assert_not_nil assigns(:date)
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_activity
|
32
|
+
get :activity
|
33
|
+
assert_response :success
|
34
|
+
assert_not_nil assigns(:messages)
|
35
|
+
assert_not_nil assigns(:bounce_types)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_show_message
|
39
|
+
get :show_message, :id => '4d95f4ebf023ca49fa000001'
|
40
|
+
assert_response :success
|
41
|
+
assert_not_nil assigns(:mail)
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_show_message_without_id
|
45
|
+
get :show_message, :id => '12345'
|
46
|
+
assert_redirected_to ses_machine_path
|
47
|
+
assert_not_nil flash[:error]
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require 'rails_generator'
|
5
|
+
require 'rails_generator/scripts/generate'
|
6
|
+
|
7
|
+
generators_path = File.join(File.dirname(__FILE__), '..', 'generators')
|
8
|
+
Rails::Generator::Base.append_sources(Rails::Generator::PathSource.new(:plugin, generators_path))
|
9
|
+
|
10
|
+
class SesMachineGeneratorTest < Test::Unit::TestCase
|
11
|
+
|
12
|
+
def setup
|
13
|
+
FileUtils.mkdir_p(fake_rails_root)
|
14
|
+
end
|
15
|
+
|
16
|
+
def teardown
|
17
|
+
FileUtils.rm_r(fake_rails_root)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_generates_correct_files
|
21
|
+
Rails::Generator::Scripts::Generate.new.run(['ses_machine'], :destination => fake_rails_root, :quiet => true)
|
22
|
+
assert File.exist?(File.join(fake_rails_root, 'config', 'ses_machine.yml'))
|
23
|
+
assert File.exist?(File.join(fake_rails_root, 'config', 'initializers', 'ses_machine.rb'))
|
24
|
+
assert File.exist?(File.join(fake_rails_root, 'config', 'initializers', 'ses_machine_hooks.rb'))
|
25
|
+
assert File.exist?(File.join(fake_rails_root, 'public', 'stylesheets', 'ses_machine.css'))
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def fake_rails_root
|
31
|
+
File.join(File.dirname(__FILE__), 'rails_root')
|
32
|
+
end
|
33
|
+
|
34
|
+
def file_list
|
35
|
+
Dir.glob(File.join(fake_rails_root, '*'))
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
data/test/test_helper.rb
ADDED
data/uninstall.rb
ADDED