malm 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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>