Permalink
Browse files

initial import of the rumbster code

  • Loading branch information...
1 parent ab36dd6 commit 6b528d1163f279fd7c5cb75e8c2219ab8efd2d05 @aesterline aesterline committed Oct 19, 2007
View
515 COPYING

Large diffs are not rendered by default.

Oops, something went wrong.
View
56 README
@@ -0,0 +1,56 @@
+Rumbster README
+===============
+
+ Rumbster is a fake smtp server for email testing in Ruby.
+ Rumbster was developed to as a way to acceptance test email
+ sending applications.
+
+Requirements
+------------
+
+ * Ruby 1.8.2 or later (may work with earlier versions)
+
+License
+-------
+
+ GNU LGPL, Lesser General Public License version 2.1
+ For details of LGPL, see file "COPYING".
+
+Example Usage
+-------------
+
+A good source for usage information is the unit tests in the
+test directory. Below is an example of the usage.
+
+class TestEmails < Test::Unit::TestCase
+
+ def setup
+ @rumbster = Rumbster.new(port)
+ @message_observer = MailMessageObserver.new
+
+ @rumbster.add_observer @message_observer
+ @rumbster.add_observer FileMessageObserver.new('some/directory')
+
+ @rumbster.start
+ end
+
+ def teardown
+ @rumbster.stop
+ end
+
+ def test_email_is_sent
+ send_email
+ assert_equal 1, @message_observer.messages.size
+ assert_equal 'junk@junk.com', @message_observer.messages.first.to
+ end
+end
+
+Bug Report
+----------
+
+ Any bug reports are welcome.
+ If you encounter a bug, please email me.
+
+ Adam Esterline
+ adam@esterlines.com
+ http://adamesterline.com
View
@@ -0,0 +1,12 @@
+require 'rake'
+require 'rake/testtask'
+
+desc "Default Task"
+task :default => [ :test ]
+
+# Run the unit tests
+Rake::TestTask.new { |t|
+ t.libs << "test"
+ t.pattern = 'test/*_test.rb'
+ t.verbose = true
+}
View
@@ -0,0 +1,40 @@
+$:.unshift(File.join(File.dirname(__FILE__), '..', 'vendor'))
+
+require 'fileutils'
+require 'tmail'
+
+class FileMessageObserver
+ include FileUtils
+
+ def initialize(message_directory, system_time = SystemTime.new)
+ @message_directory = message_directory
+ @system_time = system_time
+ mkdir_p(message_directory)
+ end
+
+ def update(message_string)
+ mail = TMail::Mail.parse(message_string)
+
+ file_name = File.join(@message_directory, "#{@system_time.current_time_in_seconds}_#{mail.to}.txt")
+ File.open(file_name, 'w') {|file| file << message_string }
+ end
+end
+
+class MailMessageObserver
+ attr_reader :messages
+
+ def initialize
+ @messages = []
+ end
+
+ def update(message_string)
+ @messages << TMail::Mail.parse(message_string)
+ end
+
+end
+
+class SystemTime
+ def current_time_in_seconds
+ Time.now.to_i
+ end
+end
View
@@ -0,0 +1,24 @@
+require 'gserver'
+require 'smtp_protocol'
+
+class Rumbster < GServer
+
+ def initialize(port=25, *args)
+ super(port, *args)
+
+ @observers = []
+ end
+
+ def serve(io)
+ @protocol = SmtpProtocol.create
+ @observers.each do |observer|
+ @protocol.add_observer(observer)
+ end
+ @protocol.serve(io)
+ end
+
+ def add_observer(observer)
+ @observers.push(observer)
+ end
+
+ end
View
@@ -0,0 +1,42 @@
+require 'observer'
+require 'smtp_states'
+
+class SmtpProtocol
+ include Observable
+
+ def SmtpProtocol.create
+ initial_state = :init
+
+ states = {
+ :init => InitState.new,
+ :connect => ConnectState.new,
+ :connected => ConnectedState.new,
+ :read_mail => ReadMailState.new,
+ :quit => QuitState.new
+ }
+
+ SmtpProtocol.new(initial_state, states)
+ end
+
+ def initialize(initial_state, states)
+ states.each_value { |state| state.protocol = self }
+
+ @states = states
+ @state = @states[initial_state]
+ end
+
+ def state=(new_state)
+ @state = @states[new_state]
+ end
+
+ def serve(io)
+ @state.serve(io)
+ end
+
+ def new_message_received(message)
+ changed
+ notify_observers(message)
+ end
+
+end
+
View
@@ -0,0 +1,159 @@
+class NotInitializedError < RuntimeError; end
+
+module State
+ attr_accessor :protocol
+
+ def serve(io)
+ raise NotInitializedError.new if @protocol.nil?
+
+ service_request(io)
+ @protocol.state = @next_state
+ @protocol.serve(io)
+ end
+end
+
+module Messages
+
+ def greeting(io)
+ io.puts '220 ruby ESMTP'
+ end
+
+ def helo_response(io)
+ io.puts '250 ruby'
+ end
+
+ def ok(io)
+ io.puts '250 ok'
+ end
+
+ def go_ahead(io)
+ io.puts '354 go ahead'
+ end
+
+ def goodbye(io)
+ io.puts '221 ruby goodbye'
+ end
+
+end
+
+class InitState
+
+ include State, Messages
+
+ def initialize(protocol = nil, next_state = :connect)
+ @protocol = protocol
+ @next_state = next_state
+ end
+
+ private
+
+ def service_request(io)
+ greeting(io)
+ end
+
+end
+
+class ConnectState
+
+ include State, Messages
+
+ def initialize(protocol = nil, next_state = :connected)
+ @protocol = protocol
+ @next_state = next_state
+ end
+
+ private
+
+ def service_request(io)
+ read_client_helo(io)
+ helo_response(io)
+ end
+
+ def read_client_helo(io)
+ io.readline
+ end
+
+end
+
+class ConnectedState
+
+ include State, Messages
+
+ def initialize(protocol = nil)
+ @protocol = protocol
+ @next_state = :connected
+ end
+
+ private
+
+ def service_request(io)
+ request = io.readline
+
+ if request.strip.eql? "DATA"
+ @next_state = :read_mail
+ go_ahead(io)
+ else
+ ok(io)
+ end
+ end
+
+end
+
+class ReadMailState
+
+ include State, Messages
+
+ def initialize(protocol = nil)
+ @protocol = protocol
+ @next_state = :quit
+ end
+
+ private
+
+ def service_request(io)
+ message = read_message(io)
+ @protocol.new_message_received(message)
+ ok(io)
+ end
+
+ def not_end_of_message(line)
+ not line.strip.eql?('.')
+ end
+
+ def read_message(io)
+ message = ''
+
+ line = io.readline
+ while not_end_of_message(line)
+ message << line
+ line = io.readline
+ end
+
+ message
+ end
+
+end
+
+class QuitState
+
+ include Messages
+ attr_accessor :protocol
+
+ def initialize(protocol = nil)
+ @protocol = protocol
+ end
+
+ def serve(io)
+ raise NotInitializedError.new if @protocol.nil?
+
+ read_quit(io)
+ goodbye(io)
+ end
+
+ private
+
+ def read_quit(io)
+ io.readline
+ end
+
+end
Oops, something went wrong.

0 comments on commit 6b528d1

Please sign in to comment.