rumbster 1.0.0

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/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