rumbster 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,56 @@
1
+ Rumbster README
2
+ ===============
3
+
4
+ Rumbster is a fake smtp server for email testing in Ruby.
5
+ Rumbster was developed to as a way to acceptance test email
6
+ sending applications.
7
+
8
+ Requirements
9
+ ------------
10
+
11
+ * Ruby 1.8.2 or later (may work with earlier versions)
12
+
13
+ License
14
+ -------
15
+
16
+ GNU LGPL, Lesser General Public License version 2.1
17
+ For details of LGPL, see file "COPYING".
18
+
19
+ Example Usage
20
+ -------------
21
+
22
+ A good source for usage information is the unit tests in the
23
+ test directory. Below is an example of the usage.
24
+
25
+ class TestEmails < Test::Unit::TestCase
26
+
27
+ def setup
28
+ @rumbster = Rumbster.new(port)
29
+ @message_observer = MailMessageObserver.new
30
+
31
+ @rumbster.add_observer @message_observer
32
+ @rumbster.add_observer FileMessageObserver.new('some/directory')
33
+
34
+ @rumbster.start
35
+ end
36
+
37
+ def teardown
38
+ @rumbster.stop
39
+ end
40
+
41
+ def test_email_is_sent
42
+ send_email
43
+ assert_equal 1, @message_observer.messages.size
44
+ assert_equal 'junk@junk.com', @message_observer.messages.first.to
45
+ end
46
+ end
47
+
48
+ Bug Report
49
+ ----------
50
+
51
+ Any bug reports are welcome.
52
+ If you encounter a bug, please email me.
53
+
54
+ Adam Esterline
55
+ adam@esterlines.com
56
+ http://adamesterline.com
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ desc "Default Task"
5
+ task :default => [ :test ]
6
+
7
+ # Run the unit tests
8
+ Rake::TestTask.new { |t|
9
+ t.libs << "test"
10
+ t.pattern = 'test/*_test.rb'
11
+ t.verbose = true
12
+ }
@@ -0,0 +1,40 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'vendor'))
2
+
3
+ require 'fileutils'
4
+ require 'tmail'
5
+
6
+ class FileMessageObserver
7
+ include FileUtils
8
+
9
+ def initialize(message_directory, system_time = SystemTime.new)
10
+ @message_directory = message_directory
11
+ @system_time = system_time
12
+ mkdir_p(message_directory)
13
+ end
14
+
15
+ def update(message_string)
16
+ mail = TMail::Mail.parse(message_string)
17
+
18
+ file_name = File.join(@message_directory, "#{@system_time.current_time_in_seconds}_#{mail.to}.txt")
19
+ File.open(file_name, 'w') {|file| file << message_string }
20
+ end
21
+ end
22
+
23
+ class MailMessageObserver
24
+ attr_reader :messages
25
+
26
+ def initialize
27
+ @messages = []
28
+ end
29
+
30
+ def update(message_string)
31
+ @messages << TMail::Mail.parse(message_string)
32
+ end
33
+
34
+ end
35
+
36
+ class SystemTime
37
+ def current_time_in_seconds
38
+ Time.now.to_i
39
+ end
40
+ end
data/lib/rumbster.rb ADDED
@@ -0,0 +1,24 @@
1
+ require 'gserver'
2
+ require 'smtp_protocol'
3
+
4
+ class Rumbster < GServer
5
+
6
+ def initialize(port=25, *args)
7
+ super(port, *args)
8
+
9
+ @observers = []
10
+ end
11
+
12
+ def serve(io)
13
+ protocol = SmtpProtocol.create
14
+ @observers.each do |observer|
15
+ protocol.add_observer(observer)
16
+ end
17
+ protocol.serve(io)
18
+ end
19
+
20
+ def add_observer(observer)
21
+ @observers.push(observer)
22
+ end
23
+
24
+ end
@@ -0,0 +1,42 @@
1
+ require 'observer'
2
+ require 'smtp_states'
3
+
4
+ class SmtpProtocol
5
+ include Observable
6
+
7
+ def SmtpProtocol.create
8
+ initial_state = :init
9
+
10
+ states = {
11
+ :init => InitState.new,
12
+ :connect => ConnectState.new,
13
+ :connected => ConnectedState.new,
14
+ :read_mail => ReadMailState.new,
15
+ :quit => QuitState.new
16
+ }
17
+
18
+ SmtpProtocol.new(initial_state, states)
19
+ end
20
+
21
+ def initialize(initial_state, states)
22
+ states.each_value { |state| state.protocol = self }
23
+
24
+ @states = states
25
+ @state = @states[initial_state]
26
+ end
27
+
28
+ def state=(new_state)
29
+ @state = @states[new_state]
30
+ end
31
+
32
+ def serve(io)
33
+ @state.serve(io)
34
+ end
35
+
36
+ def new_message_received(message)
37
+ changed
38
+ notify_observers(message)
39
+ end
40
+
41
+ end
42
+
@@ -0,0 +1,159 @@
1
+ class NotInitializedError < RuntimeError; end
2
+
3
+ module State
4
+ attr_accessor :protocol
5
+
6
+ def serve(io)
7
+ raise NotInitializedError.new if @protocol.nil?
8
+
9
+ service_request(io)
10
+ @protocol.state = @next_state
11
+ @protocol.serve(io)
12
+ end
13
+ end
14
+
15
+ module Messages
16
+
17
+ def greeting(io)
18
+ io.puts '220 ruby ESMTP'
19
+ end
20
+
21
+ def helo_response(io)
22
+ io.puts '250 ruby'
23
+ end
24
+
25
+ def ok(io)
26
+ io.puts '250 ok'
27
+ end
28
+
29
+ def go_ahead(io)
30
+ io.puts '354 go ahead'
31
+ end
32
+
33
+ def goodbye(io)
34
+ io.puts '221 ruby goodbye'
35
+ end
36
+
37
+ end
38
+
39
+ class InitState
40
+
41
+ include State, Messages
42
+
43
+ def initialize(protocol = nil, next_state = :connect)
44
+ @protocol = protocol
45
+ @next_state = next_state
46
+ end
47
+
48
+ private
49
+
50
+ def service_request(io)
51
+ greeting(io)
52
+ end
53
+
54
+ end
55
+
56
+ class ConnectState
57
+
58
+ include State, Messages
59
+
60
+ def initialize(protocol = nil, next_state = :connected)
61
+ @protocol = protocol
62
+ @next_state = next_state
63
+ end
64
+
65
+ private
66
+
67
+ def service_request(io)
68
+ read_client_helo(io)
69
+ helo_response(io)
70
+ end
71
+
72
+ def read_client_helo(io)
73
+ io.readline
74
+ end
75
+
76
+ end
77
+
78
+ class ConnectedState
79
+
80
+ include State, Messages
81
+
82
+ def initialize(protocol = nil)
83
+ @protocol = protocol
84
+ @next_state = :connected
85
+ end
86
+
87
+ private
88
+
89
+ def service_request(io)
90
+ request = io.readline
91
+
92
+ if request.strip.eql? "DATA"
93
+ @next_state = :read_mail
94
+ go_ahead(io)
95
+ else
96
+ ok(io)
97
+ end
98
+ end
99
+
100
+ end
101
+
102
+ class ReadMailState
103
+
104
+ include State, Messages
105
+
106
+ def initialize(protocol = nil)
107
+ @protocol = protocol
108
+ @next_state = :quit
109
+ end
110
+
111
+ private
112
+
113
+ def service_request(io)
114
+ message = read_message(io)
115
+ @protocol.new_message_received(message)
116
+ ok(io)
117
+ end
118
+
119
+ def not_end_of_message(line)
120
+ not line.strip.eql?('.')
121
+ end
122
+
123
+ def read_message(io)
124
+ message = ''
125
+
126
+ line = io.readline
127
+ while not_end_of_message(line)
128
+ message << line
129
+ line = io.readline
130
+ end
131
+
132
+ message
133
+ end
134
+
135
+ end
136
+
137
+ class QuitState
138
+
139
+ include Messages
140
+ attr_accessor :protocol
141
+
142
+ def initialize(protocol = nil)
143
+ @protocol = protocol
144
+ end
145
+
146
+ def serve(io)
147
+ raise NotInitializedError.new if @protocol.nil?
148
+
149
+ read_quit(io)
150
+ goodbye(io)
151
+ end
152
+
153
+ private
154
+
155
+ def read_quit(io)
156
+ io.readline
157
+ end
158
+
159
+ end
@@ -0,0 +1,102 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
3
+ require 'test/unit'
4
+ require 'fileutils'
5
+ require 'message_observers'
6
+
7
+ class MessageObserversTest < Test::Unit::TestCase
8
+
9
+ include FileUtils
10
+
11
+ def setup
12
+ @directory = File.join(File.dirname(__FILE__), '..', 'messages')
13
+ end
14
+
15
+ def teardown
16
+ rm_r(@directory) if File.exists?(@directory)
17
+ end
18
+
19
+ def test_directory_is_created_when_directory_is_not_present
20
+ file_observer = FileMessageObserver.new(@directory)
21
+
22
+ assert File.exists?(@directory)
23
+ end
24
+
25
+ def test_if_directory_is_already_present_observer_can_still_be_created
26
+ mkdir_p(@directory)
27
+
28
+ assert File.exists?(@directory)
29
+ assert_not_nil FileMessageObserver.new(@directory)
30
+ end
31
+
32
+ def test_message_received_is_saved_to_message_directory
33
+ file_observer = FileMessageObserver.new(@directory)
34
+ file_observer.update "To: junk@junk.com\nFrom: junk2@junk2.com\nSubject: What's up\n\nHi"
35
+
36
+ assert_one_new_file_added_to @directory
37
+ end
38
+
39
+ def test_message_is_saved_in_a_file_named_time_stamp_underscore_to_dot_txt
40
+ file_observer = FileMessageObserver.new(@directory, TestSystemTime.new(1))
41
+ file_observer.update "To: junk@junk.com\nFrom: junk2@junk2.com\nSubject: What's up\n\nHi"
42
+
43
+ expected_file_name = File.join(@directory, "1_junk@junk.com.txt")
44
+
45
+ assert File.exists?(expected_file_name)
46
+ end
47
+
48
+ def test_message_contents_are_saved_to_the_file
49
+ message = "To: junk@junk.com\nFrom: junk2@junk2.com\nSubject: What's up\n\nHi"
50
+
51
+ file_observer = FileMessageObserver.new(@directory, TestSystemTime.new(1))
52
+ file_observer.update message
53
+
54
+ expected_file_name = File.join(@directory, "1_junk@junk.com.txt")
55
+
56
+ assert_equal read_file_contents_into_a_string(expected_file_name), message
57
+ end
58
+
59
+ def test_messages_are_saved_for_later_inspection
60
+ message = "To: junk@junk.com\nFrom: junk2@junk2.com\nSubject: What's up\n\nHi"
61
+
62
+ mail_observer = MailMessageObserver.new
63
+ mail_observer.update message
64
+ mail_observer.update message
65
+
66
+ assert_equal 2, mail_observer.messages.size
67
+ end
68
+
69
+ def test_message_is_converted_to_tmail
70
+ message = "To: junk@junk.com\nFrom: junk2@junk2.com\nSubject: What's up\n\nHi"
71
+
72
+ mail_observer = MailMessageObserver.new
73
+ mail_observer.update message
74
+
75
+ assert_equal 'What\'s up', mail_observer.messages.first.subject
76
+ end
77
+
78
+ private
79
+
80
+ def read_file_contents_into_a_string(file_name)
81
+ File.open(file_name) {|file| file.readlines.join }
82
+ end
83
+
84
+
85
+ def assert_one_new_file_added_to(directory)
86
+ number_of_dot_and_dot_dot_entries_in_a_directory_when_it_is_first_created = 2
87
+ number_of_entries_with_one_file = number_of_dot_and_dot_dot_entries_in_a_directory_when_it_is_first_created + 1
88
+
89
+ assert_equal number_of_entries_with_one_file, Dir.entries(@directory).size
90
+ end
91
+ end
92
+
93
+ class TestSystemTime
94
+ def initialize(time)
95
+ @time = time
96
+ end
97
+
98
+ def current_time_in_seconds
99
+ @time
100
+ end
101
+
102
+ end
@@ -0,0 +1,69 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
3
+ require 'test/unit'
4
+ require 'rumbster'
5
+ require 'net/smtp'
6
+ require 'gserver'
7
+
8
+
9
+ class RumbsterTest < Test::Unit::TestCase
10
+
11
+ def setup
12
+ @observer = RumbsterObserver.new
13
+ @server = Rumbster.new(10025)
14
+ @server.add_observer(@observer)
15
+
16
+ @server.start
17
+ end
18
+
19
+ def teardown
20
+ @server.stop
21
+ end
22
+
23
+ def test_single_receiver_message_sent_by_client_is_received_by_listener
24
+ message = "From: <junk@junkster.com>\r\nTo: junk@junk.com\r\nSubject: hi\r\n\r\nThis is a test\r\n"
25
+ to = 'his_address@example.com'
26
+ send_message to, message
27
+
28
+ assert_equal message, @observer.message
29
+ end
30
+
31
+ def test_multiple_receiver_message_sent_by_client_is_received_by_listener
32
+ message = "From: <junk@junkster.com>\r\nTo: junk@junk.com\r\nSubject: hi\r\n\r\nThis is a test\r\n"
33
+ to = ['his_address@example.com', 'her_address@example.com']
34
+ send_message to, message
35
+
36
+ assert_equal message, @observer.message
37
+ end
38
+
39
+ def test_multiple_receiver_messages_sent_by_client_are_received_by_listener
40
+ message_1 = "From: <junk_1@junkster.com>\r\nTo: junk_1@junk.com\r\nSubject: hi\r\n\r\nThis is a test_1\r\n"
41
+ to_1 = ['his_address_1@example.com', 'her_address_1@example.com']
42
+ send_message to_1, message_1
43
+
44
+ assert_equal message_1, @observer.message
45
+
46
+ message_2 = "From: <junk_2@junkster.com>\r\nTo: junk_2@junk.com\r\nSubject: hi\r\n\r\nThis is a test_2\r\n"
47
+ to_2 = ['his_address_2@example.com', 'her_address_2@example.com']
48
+ send_message to_2, message_2
49
+
50
+ assert_equal message_2, @observer.message
51
+ end
52
+
53
+ private
54
+
55
+ def send_message(to, message)
56
+ Net::SMTP.start('localhost', 10025) do |smtp|
57
+ smtp.send_message message, 'your@mail.address', to
58
+ end
59
+ end
60
+
61
+ end
62
+
63
+ class RumbsterObserver
64
+ attr_accessor :message
65
+
66
+ def update(message)
67
+ @message = message
68
+ end
69
+ end