axtro-aws-ses 0.4.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/CHANGELOG +42 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +28 -0
- data/LICENSE +20 -0
- data/README.erb +92 -0
- data/README.rdoc +196 -0
- data/Rakefile +82 -0
- data/TODO +3 -0
- data/VERSION +1 -0
- data/aws-ses.gemspec +102 -0
- data/axtro-aws-ses.gemspec +104 -0
- data/lib/aws/actionmailer/ses_extension.rb +19 -0
- data/lib/aws/ses.rb +29 -0
- data/lib/aws/ses/addresses.rb +75 -0
- data/lib/aws/ses/base.rb +176 -0
- data/lib/aws/ses/expirable_memoize.rb +45 -0
- data/lib/aws/ses/info.rb +103 -0
- data/lib/aws/ses/response.rb +113 -0
- data/lib/aws/ses/send_email.rb +156 -0
- data/lib/aws/ses/version.rb +12 -0
- data/test/address_test.rb +72 -0
- data/test/base_test.rb +40 -0
- data/test/expirable_memoize_test.rb +120 -0
- data/test/fixtures.rb +89 -0
- data/test/helper.rb +56 -0
- data/test/info_test.rb +108 -0
- data/test/mocks/fake_response.rb +26 -0
- data/test/response_test.rb +26 -0
- data/test/send_email_test.rb +94 -0
- metadata +215 -0
data/test/base_test.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
class BaseTest < Test::Unit::TestCase
|
4
|
+
def test_connection_established
|
5
|
+
instance = Base.new(:access_key_id => '123', :secret_access_key => 'abc')
|
6
|
+
|
7
|
+
assert_not_nil instance.instance_variable_get("@http")
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_failed_response
|
11
|
+
@base = generate_base
|
12
|
+
mock_connection(@base, {:code => 403, :body => %{
|
13
|
+
<ErrorResponse>
|
14
|
+
<Error>
|
15
|
+
<Type>
|
16
|
+
Sender
|
17
|
+
</Type>
|
18
|
+
<Code>
|
19
|
+
ValidationError
|
20
|
+
</Code>
|
21
|
+
<Message>
|
22
|
+
Value null at 'message.subject' failed to satisfy constraint: Member must not be null
|
23
|
+
</Message>
|
24
|
+
</Error>
|
25
|
+
<RequestId>
|
26
|
+
42d59b56-7407-4c4a-be0f-4c88daeea257
|
27
|
+
</RequestId>
|
28
|
+
</ErrorResponse>
|
29
|
+
}})
|
30
|
+
|
31
|
+
assert_raises ResponseError do
|
32
|
+
result = @base.request('', {})
|
33
|
+
end
|
34
|
+
|
35
|
+
# assert !result.success?
|
36
|
+
# assert result.error?
|
37
|
+
# assert result.error.error?
|
38
|
+
# assert_equal 'ValidationError', result.error.code
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
class KerneltExtensionsTest < Test::Unit::TestCase
|
4
|
+
class Foo
|
5
|
+
include AWS::SES::ExpirableMemoize::InstanceMethods
|
6
|
+
extend AWS::SES::ExpirableMemoize::ClassMethods
|
7
|
+
|
8
|
+
def foo
|
9
|
+
__method__
|
10
|
+
end
|
11
|
+
|
12
|
+
def bar
|
13
|
+
foo
|
14
|
+
end
|
15
|
+
|
16
|
+
def baz
|
17
|
+
bar
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Bar
|
22
|
+
include AWS::SES::ExpirableMemoize::InstanceMethods
|
23
|
+
extend AWS::SES::ExpirableMemoize::ClassMethods
|
24
|
+
|
25
|
+
def foo
|
26
|
+
calling_method
|
27
|
+
end
|
28
|
+
|
29
|
+
def bar
|
30
|
+
calling_method
|
31
|
+
end
|
32
|
+
|
33
|
+
def calling_method
|
34
|
+
__method__(1)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def test___method___works_regardless_of_nesting
|
39
|
+
f = Foo.new
|
40
|
+
[:foo, :bar, :baz].each do |method|
|
41
|
+
assert_equal 'foo', f.send(method)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def test___method___depth
|
46
|
+
b = Bar.new
|
47
|
+
assert_equal 'foo', b.foo
|
48
|
+
assert_equal 'bar', b.bar
|
49
|
+
end
|
50
|
+
end if RUBY_VERSION <= '1.8.7'
|
51
|
+
|
52
|
+
class ModuleExtensionsTest < Test::Unit::TestCase
|
53
|
+
class Foo
|
54
|
+
include AWS::SES::ExpirableMemoize::InstanceMethods
|
55
|
+
extend AWS::SES::ExpirableMemoize::ClassMethods
|
56
|
+
|
57
|
+
def foo(reload = false)
|
58
|
+
expirable_memoize(reload) do
|
59
|
+
Time.now
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def bar(reload = false)
|
64
|
+
expirable_memoize(reload, :baz) do
|
65
|
+
Time.now
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def quux
|
70
|
+
Time.now
|
71
|
+
end
|
72
|
+
memoized :quux
|
73
|
+
end
|
74
|
+
|
75
|
+
def setup
|
76
|
+
@instance = Foo.new
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_memoize
|
80
|
+
assert !instance_variables_of(@instance).include?('@foo')
|
81
|
+
cached_result = @instance.foo
|
82
|
+
assert_equal cached_result, @instance.foo
|
83
|
+
assert instance_variables_of(@instance).include?('@foo')
|
84
|
+
assert_equal cached_result, @instance.send(:instance_variable_get, :@foo)
|
85
|
+
assert_not_equal cached_result, new_cache = @instance.foo(:reload)
|
86
|
+
assert_equal new_cache, @instance.foo
|
87
|
+
assert_equal new_cache, @instance.send(:instance_variable_get, :@foo)
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_customizing_memoize_storage
|
91
|
+
assert !instance_variables_of(@instance).include?('@bar')
|
92
|
+
assert !instance_variables_of(@instance).include?('@baz')
|
93
|
+
cached_result = @instance.bar
|
94
|
+
assert !instance_variables_of(@instance).include?('@bar')
|
95
|
+
assert instance_variables_of(@instance).include?('@baz')
|
96
|
+
assert_equal cached_result, @instance.bar
|
97
|
+
assert_equal cached_result, @instance.send(:instance_variable_get, :@baz)
|
98
|
+
assert_nil @instance.send(:instance_variable_get, :@bar)
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_memoized
|
102
|
+
assert !instance_variables_of(@instance).include?('@quux')
|
103
|
+
cached_result = @instance.quux
|
104
|
+
assert_equal cached_result, @instance.quux
|
105
|
+
assert instance_variables_of(@instance).include?('@quux')
|
106
|
+
assert_equal cached_result, @instance.send(:instance_variable_get, :@quux)
|
107
|
+
assert_not_equal cached_result, new_cache = @instance.quux(:reload)
|
108
|
+
assert_equal new_cache, @instance.quux
|
109
|
+
assert_equal new_cache, @instance.send(:instance_variable_get, :@quux)
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
# For 1.9 compatibility
|
114
|
+
def instance_variables_of(object)
|
115
|
+
object.instance_variables.map do |instance_variable|
|
116
|
+
instance_variable.to_s
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
data/test/fixtures.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module AWS
|
4
|
+
module SES
|
5
|
+
# When this file is loaded, for each fixture file, a module is created within the Fixtures module
|
6
|
+
# with the same name as the fixture file. For each fixture in that fixture file, a singleton method is
|
7
|
+
# added to the module with the name of the given fixture, returning the value of the fixture.
|
8
|
+
#
|
9
|
+
# For example:
|
10
|
+
#
|
11
|
+
# A fixture in <tt>buckets.yml</tt> named <tt>empty_bucket_list</tt> with value <tt><foo>hi!</foo></tt>
|
12
|
+
# would be made available like so:
|
13
|
+
#
|
14
|
+
# Fixtures::Buckets.empty_bucket_list
|
15
|
+
# => "<foo>hi!</foo>"
|
16
|
+
#
|
17
|
+
# Alternatively you can treat the fixture module like a hash
|
18
|
+
#
|
19
|
+
# Fixtures::Buckets[:empty_bucket_list]
|
20
|
+
# => "<foo>hi!</foo>"
|
21
|
+
#
|
22
|
+
# You can find out all available fixtures by calling
|
23
|
+
#
|
24
|
+
# Fixtures.fixtures
|
25
|
+
# => ["Buckets"]
|
26
|
+
#
|
27
|
+
# And all the fixtures contained in a given fixture by calling
|
28
|
+
#
|
29
|
+
# Fixtures::Buckets.fixtures
|
30
|
+
# => ["bucket_list_with_more_than_one_bucket", "bucket_list_with_one_bucket", "empty_bucket_list"]
|
31
|
+
module Fixtures
|
32
|
+
class << self
|
33
|
+
def create_fixtures
|
34
|
+
files.each do |file|
|
35
|
+
create_fixture_for(file)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def create_fixture_for(file)
|
40
|
+
fixtures = YAML.load_file(path(file))
|
41
|
+
fixture_module = Module.new
|
42
|
+
|
43
|
+
fixtures.each do |name, value|
|
44
|
+
fixture_module.module_eval(<<-EVAL, __FILE__, __LINE__)
|
45
|
+
def #{name}
|
46
|
+
#{value.inspect}
|
47
|
+
end
|
48
|
+
module_function :#{name}
|
49
|
+
EVAL
|
50
|
+
end
|
51
|
+
|
52
|
+
fixture_module.module_eval(<<-EVAL, __FILE__, __LINE__)
|
53
|
+
module_function
|
54
|
+
|
55
|
+
def fixtures
|
56
|
+
#{fixtures.keys.sort.inspect}
|
57
|
+
end
|
58
|
+
|
59
|
+
def [](name)
|
60
|
+
send(name) if fixtures.include?(name.to_s)
|
61
|
+
end
|
62
|
+
EVAL
|
63
|
+
|
64
|
+
const_set(module_name(file), fixture_module)
|
65
|
+
end
|
66
|
+
|
67
|
+
def fixtures
|
68
|
+
constants.sort
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def files
|
74
|
+
Dir.glob(File.dirname(__FILE__) + '/fixtures/*.yml').map {|fixture| File.basename(fixture)}
|
75
|
+
end
|
76
|
+
|
77
|
+
def module_name(file_name)
|
78
|
+
File.basename(file_name, '.*').capitalize
|
79
|
+
end
|
80
|
+
|
81
|
+
def path(file_name)
|
82
|
+
File.join(File.dirname(__FILE__), 'fixtures', file_name)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
create_fixtures
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
require 'shoulda-context'
|
12
|
+
|
13
|
+
begin
|
14
|
+
require 'ruby-debug'
|
15
|
+
rescue LoadError
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'flexmock'
|
19
|
+
require 'flexmock/test_unit'
|
20
|
+
|
21
|
+
require File.dirname(__FILE__) + '/mocks/fake_response'
|
22
|
+
require File.dirname(__FILE__) + '/fixtures'
|
23
|
+
|
24
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
25
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
26
|
+
require 'aws/ses'
|
27
|
+
|
28
|
+
class Test::Unit::TestCase
|
29
|
+
require 'net/http'
|
30
|
+
require 'net/https'
|
31
|
+
|
32
|
+
include AWS::SES
|
33
|
+
|
34
|
+
def mock_connection(object, data = {})
|
35
|
+
return_values = case data
|
36
|
+
when Hash
|
37
|
+
FakeResponse.new(data)
|
38
|
+
when Array
|
39
|
+
data.map {|hash| FakeResponse.new(hash)}
|
40
|
+
else
|
41
|
+
abort "Response data for mock connection must be a Hash or an Array. Was #{data.inspect}."
|
42
|
+
end
|
43
|
+
|
44
|
+
connection = flexmock('Net::HTTP.new') do |mock|
|
45
|
+
mock.should_receive(:post).and_return(*return_values).at_least.once
|
46
|
+
end
|
47
|
+
|
48
|
+
mock = flexmock(object)
|
49
|
+
mock.should_receive(:connection).and_return(connection)
|
50
|
+
mock
|
51
|
+
end
|
52
|
+
|
53
|
+
def generate_base
|
54
|
+
Base.new(:access_key_id=>'123', :secret_access_key=>'abc')
|
55
|
+
end
|
56
|
+
end
|
data/test/info_test.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
class InfoTest < Test::Unit::TestCase
|
4
|
+
context 'getting send quota' do
|
5
|
+
setup do
|
6
|
+
@base = generate_base
|
7
|
+
end
|
8
|
+
|
9
|
+
should 'return the correct response on success' do
|
10
|
+
mock_connection(@base, :body => %{
|
11
|
+
<GetSendQuotaResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/">
|
12
|
+
<GetSendQuotaResult>
|
13
|
+
<SentLast24Hours>0.0</SentLast24Hours>
|
14
|
+
<Max24HourSend>1000.0</Max24HourSend>
|
15
|
+
<MaxSendRate>1.0</MaxSendRate>
|
16
|
+
</GetSendQuotaResult>
|
17
|
+
<ResponseMetadata>
|
18
|
+
<RequestId>abc-123</RequestId>
|
19
|
+
</ResponseMetadata>
|
20
|
+
</GetSendQuotaResponse>
|
21
|
+
})
|
22
|
+
|
23
|
+
result = @base.quota
|
24
|
+
assert result.success?
|
25
|
+
assert_equal 'abc-123', result.request_id
|
26
|
+
assert_equal '0.0', result.sent_last_24_hours
|
27
|
+
assert_equal '1000.0', result.max_24_hour_send
|
28
|
+
assert_equal '1.0', result.max_send_rate
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'getting send statistics' do
|
33
|
+
setup do
|
34
|
+
@base = generate_base
|
35
|
+
end
|
36
|
+
|
37
|
+
should 'return the correct response on success' do
|
38
|
+
mock_connection(@base, :body => %{
|
39
|
+
<GetSendStatisticsResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/">
|
40
|
+
<GetSendStatisticsResult>
|
41
|
+
<SendDataPoints>
|
42
|
+
<member>
|
43
|
+
<DeliveryAttempts>3</DeliveryAttempts>
|
44
|
+
<Timestamp>2011-01-31T15:31:00Z</Timestamp>
|
45
|
+
<Rejects>2</Rejects>
|
46
|
+
<Bounces>4</Bounces>
|
47
|
+
<Complaints>1</Complaints>
|
48
|
+
</member>
|
49
|
+
<member>
|
50
|
+
<DeliveryAttempts>3</DeliveryAttempts>
|
51
|
+
<Timestamp>2011-01-31T16:01:00Z</Timestamp>
|
52
|
+
<Rejects>0</Rejects>
|
53
|
+
<Bounces>0</Bounces>
|
54
|
+
<Complaints>0</Complaints>
|
55
|
+
</member>
|
56
|
+
<member>
|
57
|
+
<DeliveryAttempts>1</DeliveryAttempts>
|
58
|
+
<Timestamp>2011-01-26T16:31:00Z</Timestamp>
|
59
|
+
<Rejects>0</Rejects>
|
60
|
+
<Bounces>0</Bounces>
|
61
|
+
<Complaints>0</Complaints>
|
62
|
+
</member>
|
63
|
+
</SendDataPoints>
|
64
|
+
</GetSendStatisticsResult>
|
65
|
+
<ResponseMetadata>
|
66
|
+
<RequestId>abc-123</RequestId>
|
67
|
+
</ResponseMetadata>
|
68
|
+
</GetSendStatisticsResponse>
|
69
|
+
})
|
70
|
+
|
71
|
+
result = @base.statistics
|
72
|
+
|
73
|
+
assert result.success?
|
74
|
+
assert_equal 'abc-123', result.request_id
|
75
|
+
|
76
|
+
assert_equal 3, result.data_points.size
|
77
|
+
|
78
|
+
d = result.data_points.first
|
79
|
+
|
80
|
+
assert_equal '2', d['Rejects']
|
81
|
+
assert_equal '3', d['DeliveryAttempts']
|
82
|
+
assert_equal '4', d['Bounces']
|
83
|
+
assert_equal '1', d['Complaints']
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
context 'deleting a verified addressess' do
|
89
|
+
setup do
|
90
|
+
@base = generate_base
|
91
|
+
end
|
92
|
+
|
93
|
+
should 'return the correct response on success' do
|
94
|
+
mock_connection(@base, :body => %{
|
95
|
+
<DeleteVerifiedEmailAddressResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/">
|
96
|
+
<ResponseMetadata>
|
97
|
+
<RequestId>abc-123</RequestId>
|
98
|
+
</ResponseMetadata>
|
99
|
+
</DeleteVerifiedEmailAddressResponse>
|
100
|
+
})
|
101
|
+
|
102
|
+
result = @base.addresses.delete('user1@example.com')
|
103
|
+
|
104
|
+
assert result.success?
|
105
|
+
assert_equal 'abc-123', result.request_id
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module AWS
|
2
|
+
module SES
|
3
|
+
class FakeResponse
|
4
|
+
attr_reader :code, :body, :headers
|
5
|
+
def initialize(options = {})
|
6
|
+
@code = options.delete(:code) || 200
|
7
|
+
@body = options.delete(:body) || ''
|
8
|
+
@headers = {'content-type' => 'application/xml'}.merge(options.delete(:headers) || {})
|
9
|
+
end
|
10
|
+
|
11
|
+
# For ErrorResponse
|
12
|
+
def response
|
13
|
+
body
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](header)
|
17
|
+
headers[header]
|
18
|
+
end
|
19
|
+
|
20
|
+
def each(&block)
|
21
|
+
headers.each(&block)
|
22
|
+
end
|
23
|
+
alias_method :each_header, :each
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
class BaseResponseTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@headers = {'content-type' => 'text/plain', 'date' => Time.now}
|
6
|
+
@response = FakeResponse.new()
|
7
|
+
@base_response = Response.new('ResponseAction', @response)
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_status_predicates
|
11
|
+
response = Proc.new {|code| Response.new('ResponseAction', FakeResponse.new(:code => code))}
|
12
|
+
assert response[200].success?
|
13
|
+
assert response[300].redirect?
|
14
|
+
assert response[400].client_error?
|
15
|
+
assert response[500].server_error?
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_headers_passed_along_from_original_response
|
19
|
+
assert_equal @response.headers, @base_response.headers
|
20
|
+
assert_equal @response['date'], @base_response['date']
|
21
|
+
original_headers, new_headers = {}, {}
|
22
|
+
@response.headers.each {|k,v| original_headers[k] = v}
|
23
|
+
@base_response.each {|k,v| new_headers[k] = v}
|
24
|
+
assert_equal original_headers, new_headers
|
25
|
+
end
|
26
|
+
end
|