malm 0.0.6 → 0.1.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.
@@ -0,0 +1,160 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Malm::Message do
4
+ before(:each) do
5
+ @message = Malm::Message.new
6
+ end
7
+
8
+ describe "#process_line" do
9
+ describe "in INIT state" do
10
+ it "responds with hello" do
11
+ @message.process_line(:any_old_input)[:output].should == "220 hello\r\n"
12
+ end
13
+
14
+ it "should transition to HELO state on any input" do
15
+ @message.process_line(nil)
16
+ @message.state.should == :helo_state
17
+ end
18
+ end
19
+
20
+ describe "in HELO state" do
21
+ before(:each) do
22
+ @message.process_line(nil) # transition to HELO state
23
+ end
24
+
25
+ describe "input 'HELO'" do
26
+ it "should be OK" do
27
+ @message.process_line("HELO")[:output].should == "250 OK\r\n"
28
+ end
29
+ it "should transition to MAIL state" do
30
+ @message.process_line("HELO")
31
+ @message.state.should == :mail_state
32
+ end
33
+ end
34
+
35
+ describe "input 'EHLO'" do
36
+ it "should be OK" do
37
+ @message.process_line("EHLO")[:output].should == "250 OK\r\n"
38
+ end
39
+ it "should transition to MAIL state" do
40
+ @message.process_line("EHLO")
41
+ @message.state.should == :mail_state
42
+ end
43
+ end
44
+ end
45
+
46
+ describe "in MAIL state" do
47
+ before(:each) do
48
+ @message.process_line(nil) # transition to HELO state
49
+ @message.process_line("HELO\r\n") # transition to MAIL state
50
+ end
51
+
52
+ describe "input MAIL FROM <...>" do
53
+ before(:each) do
54
+ @reponse = @message.process_line("MAIL FROM:<francis@example.com>\r\n")
55
+ end
56
+
57
+ it "should be OK" do
58
+ @reponse[:output].should == "250 OK\r\n"
59
+ end
60
+
61
+ it "should transition to RCPT state" do
62
+ @message.state.should == :rcpt_state
63
+ end
64
+
65
+ it "should set the from address" do
66
+ @message.mail_from.should == "francis@example.com"
67
+ end
68
+ end
69
+ end
70
+
71
+ describe "in RCPT state" do
72
+ before(:each) do
73
+ @message.process_line(nil) # transition to HELO state
74
+ @message.process_line("HELO\r\n") # transition to MAIL state
75
+ @message.process_line("MAIL FROM:<francis@example.com>\r\n") # transition to RCPT state
76
+ end
77
+
78
+ describe "input RCPT TO:<...>" do
79
+ before(:each) do
80
+ @reponse = @message.process_line("RCPT TO:<louis@example.com>\r\n")
81
+ end
82
+
83
+ it "adds the recipient" do
84
+ @message.rcpt_to.should == ["louis@example.com"]
85
+ end
86
+
87
+ it "does not transition state" do
88
+ @message.state.should == :rcpt_state
89
+ end
90
+
91
+ it "should be OK" do
92
+ @reponse[:output].should == "250 OK\r\n"
93
+ end
94
+
95
+ end
96
+
97
+ describe "input DATA" do
98
+ before(:each) do
99
+ @reponse = @message.process_line("DATA\r\n")
100
+ end
101
+
102
+ it "should be 354 response" do
103
+ @reponse[:output].should == "354 Enter message, ending with \".\" on a line by itself\r\n"
104
+ end
105
+
106
+ it "should transition to DATA state" do
107
+ @message.state.should == :data_state
108
+ end
109
+ end
110
+ end
111
+
112
+ describe "in DATA state" do
113
+ before(:each) do
114
+ @message.process_line(nil) # transition to HELO state
115
+ @message.process_line("HELO\r\n") # transition to MAIL state
116
+ @message.process_line("MAIL FROM:<francis@example.com>\r\n") # transition to RCPT state
117
+ @message.process_line("RCPT TO:<louis@example.com>\r\n") # transition to DATA state
118
+ @message.process_line("DATA\r\n") # transition to DATA state
119
+ end
120
+
121
+ it "accumulates data" do
122
+ @message.process_line("line 1\r\n")
123
+ @message.process_line("line 2\r\n")
124
+ @message.process_line("line 3\r\n")
125
+ @message.email_body.should == "line 1\r\nline 2\r\nline 3\r\n"
126
+ end
127
+
128
+ describe "input ." do
129
+ before(:each) do
130
+ @message.process_line("line 1\r\n")
131
+ @response = @message.process_line(".\r\n")
132
+ end
133
+
134
+ it "should be OK" do
135
+ @response[:output].should == "250 OK\r\n"
136
+ end
137
+
138
+ it "should transition to QUIT state" do
139
+ @message.state.should == :quit_state
140
+ end
141
+
142
+ end
143
+ end
144
+ end
145
+
146
+ describe "#subject" do
147
+ it "should be extracted from email body" do
148
+ @message.process_line(nil) # transition to HELO state
149
+ @message.process_line("HELO\r\n") # transition to MAIL state
150
+ @message.process_line("MAIL FROM:<francis@example.com>\r\n") # transition to RCPT state
151
+ @message.process_line("RCPT TO:<louis@example.com>\r\n") # transition to DATA state
152
+ @message.process_line("DATA\r\n") # transition to DATA state
153
+
154
+ @message.process_line("Subject: This is a subject\r\n")
155
+ @message.process_line("This is NOT a subject\r\n")
156
+
157
+ @message.subject.should == "This is a subject"
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,44 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Malm::SMTPServer do
4
+ before do
5
+ @db = mock("db")
6
+ @io = mock("io")
7
+ end
8
+
9
+ describe "SMTP conversation" do
10
+ it "should receive a message" do
11
+ @io.should_receive(:closed?).at_least(:once).and_return(false)
12
+
13
+ @io.should_receive(:gets).and_return(
14
+ "HELO smtp.example.com\r\n",
15
+ "MAIL FROM:<from@example.com>\r\n",
16
+ "RCPT TO:<receiver1@example.com>\r\n",
17
+ "RCPT TO:<receiver2@example.com>\r\n",
18
+ "DATA\r\n",
19
+ "Subject: This is a test\r\n",
20
+ "This is a line\r\n",
21
+ "This is another line\r\n",
22
+ ".\r\n",
23
+ "QUIT\r\n"
24
+ )
25
+
26
+ @io.should_receive(:print).with("220 hello\r\n").ordered
27
+ @io.should_receive(:print).with("250 OK\r\n").exactly(5).times.ordered
28
+ @io.should_receive(:print).with("354 Enter message, ending with \".\" on a line by itself\r\n").ordered
29
+ @io.should_receive(:print).with("221 bye\r\n").ordered
30
+ @io.should_receive(:close).ordered
31
+
32
+ @db.should_receive(:create).with(
33
+ :subject => "This is a test",
34
+ :from => "from@example.com",
35
+ :to => ["receiver1@example.com", "receiver2@example.com"],
36
+ :body => "Subject: This is a test\r\nThis is a line\r\nThis is another line\r\n")
37
+
38
+ smtp_server = Malm::SMTPServer.new(2525)
39
+ smtp_server.message_db = @db
40
+
41
+ smtp_server.serve(@io)
42
+ end
43
+ end
44
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
2
  $LOAD_PATH.unshift(File.dirname(__FILE__))
3
3
  require 'rspec'
4
- require 'help'
4
+ require 'malm'
5
5
 
6
6
  # Requires supporting files with custom matchers and macros, etc,
7
7
  # in ./support/ and its subdirectories.
@@ -81,7 +81,26 @@ a:hover { color: #036; }
81
81
  * Author:
82
82
  */
83
83
 
84
+ /* list/content layout shamelessly stolen from http://documentcloud.github.com/backbone/ */
85
+ #messages {
86
+ position: fixed; top: 0; left: 0; bottom: 0; width: 200px;
87
+ background: white;
88
+ overflow-y: auto;
89
+ overflow-x: hidden;
90
+ padding: 15px 0 30px 30px;
91
+ border-right: 1px solid #DDD;
92
+ box-shadow: 0 0 20px #CCC;
93
+ -webkit-box-shadow: 0 0 20px #CCC;
94
+ -moz-box-shadow: 0 0 20px #CCC;
95
+ }
84
96
 
97
+ #message {
98
+ position: relative;
99
+ width: 700px;
100
+ margin: 40px 0 50px 260px;
101
+ overflow-x: visible;
102
+ overflow-y: visible;
103
+ }
85
104
 
86
105
 
87
106
 
File without changes
@@ -0,0 +1 @@
1
+ *.js
File without changes
@@ -0,0 +1,106 @@
1
+ @Malm = {}
2
+
3
+ @Malm.start = () ->
4
+ messageList = new MessageList()
5
+ messageRouter = new MessageRouter()
6
+ messageListView = new MessageListView("messageRouter": messageRouter, "collection": messageList)
7
+ messageBodyView = new MessageBodyView()
8
+
9
+ messageController = new MessageController(
10
+ "messageList" : messageList
11
+ "messageListView" : messageListView
12
+ "messageBodyView" : messageBodyView
13
+ )
14
+ messageRouter.messageController = messageController
15
+ messageListView.messageController = messageController
16
+
17
+ messageList.fetch("success": () -> Backbone.history.start())
18
+
19
+ class Message extends Backbone.Model
20
+
21
+
22
+ class MessageList extends Backbone.Collection
23
+ model: Message
24
+ url: "/messages"
25
+
26
+ comparator: (message) ->
27
+ message.id * -1
28
+
29
+
30
+ class MessageListView extends Backbone.View
31
+ el: '#messagesContent'
32
+
33
+ events:
34
+ 'click .reload a' : 'reload'
35
+
36
+ initialize: () ->
37
+ @messageRouter = @options.messageRouter
38
+ @collection.bind('reset', @resetMessages)
39
+
40
+ resetMessages: (messages) =>
41
+ messages.each (message) =>
42
+ view = new MessageView("model": message, "messageRouter": @messageRouter)
43
+ $(@el).append(view.el)
44
+
45
+ reload: () ->
46
+ $(".messagesItem").remove()
47
+ @messageController.reload()
48
+
49
+
50
+ class MessageView extends Backbone.View
51
+ tagName: 'div'
52
+ className: 'messagesItem'
53
+
54
+ events:
55
+ 'click .contentTypeLinks a' : 'showMessage'
56
+
57
+ initialize: () ->
58
+ @template = $('#messageViewTemplate').html()
59
+ @messageRouter = @options.messageRouter
60
+ @render()
61
+
62
+ render: () ->
63
+ html = Mustache.to_html(@template,
64
+ "subject": @model.get("subject"),
65
+ "htmlUrl": @model.get("body_urls").html,
66
+ "textUrl": @model.get("body_urls").text
67
+ )
68
+ $(@el).append(html)
69
+
70
+ showMessage: (e) ->
71
+ contentType = e.target.className
72
+ @messageRouter.navigate("/messages/#{@model.id}/body.#{contentType}", true)
73
+
74
+
75
+ class MessageBodyView extends Backbone.View
76
+ el: "#messageBody"
77
+
78
+ initialize: () ->
79
+ @template = $('#messageBodyTemplate').html()
80
+ @contentType = @options.contentType
81
+
82
+ render: () ->
83
+ html = Mustache.to_html(@template, "bodyUrl": @model.get("body_urls")[@contentType])
84
+ $(@el).html(html)
85
+
86
+ class MessageController
87
+ constructor: (options) ->
88
+ @messageList = options.messageList
89
+
90
+ showMessage: (messageId, contentType) ->
91
+ message = @messageList.get(messageId)
92
+ bodyView = new MessageBodyView("model":message, "contentType": contentType)
93
+ bodyView.render()
94
+
95
+ reload: () ->
96
+ @messageList.reset()
97
+ @messageList.fetch()
98
+
99
+
100
+ class MessageRouter extends Backbone.Router
101
+ routes: {
102
+ '/messages/:id/body.:contentType' : 'showMesage'
103
+ }
104
+
105
+ showMesage: (messageId, contentType) ->
106
+ @messageController.showMessage(messageId, contentType)
@@ -0,0 +1,79 @@
1
+ <!doctype>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
6
+
7
+ <title>malm</title>
8
+ <meta name="description" content="malm mail server web view">
9
+ <meta name="author" content="">
10
+
11
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
12
+ <link rel="shortcut icon" href="/favicon.ico">
13
+
14
+ <link rel="stylesheet" href="css/style.css?v=2">
15
+
16
+ <script type="text/html" id="messageViewTemplate">
17
+ <p>
18
+ <div class="subject">{{subject}}</div>
19
+ <div class="contentTypeLinks">
20
+ {{#htmlUrl}}
21
+ <a class="html" href="javascript: void(0)">HTML</a>
22
+ {{/htmlUrl}}
23
+ {{#textUrl}}
24
+ <a class="text" href="javascript: void(0)">plain text</a>
25
+ {{/textUrl}}
26
+ </div>
27
+ </p>
28
+ </script>
29
+
30
+ <script type="text/html" id="messageBodyTemplate">
31
+ <span>Subject:</span> <span>{{message.subject}}</p>
32
+ <iframe src="{{bodyUrl}}" style="width:100%;height:100%"/>
33
+ </script>
34
+
35
+
36
+ </head>
37
+
38
+ <body>
39
+
40
+ <div id="container">
41
+ <header>
42
+ <h1>malm</h1>
43
+ </header>
44
+
45
+ <div id="main" role="main">
46
+ <div id="messages">
47
+ <h2>Messages</h2>
48
+ <div id="messagesContent">
49
+ <div class="reload"><a href="javascript: void(0)">Reload</a></div>
50
+
51
+ </div>
52
+ </div>
53
+ <div id="message">
54
+ <h1>Message</h1>
55
+ <div id="messageBody"><p>Click message to load content...</p></div>
56
+ </div>
57
+ </div>
58
+
59
+ <footer>
60
+ <p>I blame <a href="https://twitter.com/#!/madlep">@madlep</a> for <a href="https://github.com/madlep/malm">malm</a></p>
61
+ </footer>
62
+ </div> <!-- eo #container -->
63
+
64
+ <script src="/js/lib/jquery-1.6.2.min.js"></script>
65
+ <script src="/js/lib/underscore-min.js"></script>
66
+ <script src="/js/lib/backbone-min.js"></script>
67
+ <script src="/js/lib/mustache.js"></script>
68
+
69
+ <script src="/js/malm.js"></script>
70
+
71
+
72
+ <script type="text/javascript">
73
+ $(function(){
74
+ Malm.start();
75
+ });
76
+ </script>
77
+
78
+ </body>
79
+ </html>