aboutsummaryrefslogtreecommitdiffstats
path: root/spec/models/mail_server_log_spec.rb
blob: 2b44a45591fcd0538e8aaf82e0d36a61422fc3ad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe MailServerLog do
    describe ".load_file" do
        before :each do
            AlaveteliConfiguration.stub!(:incoming_email_domain).and_return("example.com")
            File.stub_chain(:stat, :mtime).and_return(DateTime.new(2012, 10, 10))
        end

        let(:log) {[
            "This is a line of a logfile relevant to foi+request-1234@example.com",
            "This is the second line for the same foi+request-1234@example.com email address"
        ]}

        let(:ir) { info_requests(:fancy_dog_request) }

        it "loads relevant lines of an uncompressed exim log file" do
            File.should_receive(:open).with("/var/log/exim4/exim-mainlog-2012-10-10", "r").and_return(log)
            InfoRequest.should_receive(:find_by_incoming_email).with("request-1234@example.com").twice.and_return(ir)
            MailServerLog.load_file("/var/log/exim4/exim-mainlog-2012-10-10")

            ir.mail_server_logs.count.should == 2
            log = ir.mail_server_logs[0]
            log.order.should == 1
            log.line.should == "This is a line of a logfile relevant to foi+request-1234@example.com"

            log = ir.mail_server_logs[1]
            log.order.should == 2
            log.line.should == "This is the second line for the same foi+request-1234@example.com email address"
        end

        it "doesn't load the log file twice if it's unchanged" do
            File.should_receive(:open).with("/var/log/exim4/exim-mainlog-2012-10-10", "r").once.and_return([])

            MailServerLog.load_file("/var/log/exim4/exim-mainlog-2012-10-10")
            MailServerLog.load_file("/var/log/exim4/exim-mainlog-2012-10-10")
        end

        it "loads the log file again if it's changed" do
            File.should_receive(:open).with("/var/log/exim4/exim-mainlog-2012-10-10", "r").twice.and_return([])
            MailServerLog.load_file("/var/log/exim4/exim-mainlog-2012-10-10")
            File.stub_chain(:stat, :mtime).and_return(DateTime.new(2012, 10, 11))
            MailServerLog.load_file("/var/log/exim4/exim-mainlog-2012-10-10")
        end

        it "doesn't end up with two copies of each line when the same file is actually loaded twice" do
            File.should_receive(:open).with("/var/log/exim4/exim-mainlog-2012-10-10", "r").twice.and_return(log)
            InfoRequest.should_receive(:find_by_incoming_email).with("request-1234@example.com").any_number_of_times.and_return(ir)

            MailServerLog.load_file("/var/log/exim4/exim-mainlog-2012-10-10")
            ir.mail_server_logs.count.should == 2

            File.stub_chain(:stat, :mtime).and_return(DateTime.new(2012, 10, 11))
            MailServerLog.load_file("/var/log/exim4/exim-mainlog-2012-10-10")
            ir.mail_server_logs.count.should == 2
        end

        it "easily handles gzip compress log files" do
            File.should_not_receive(:open)
            Zlib::GzipReader.should_receive(:open).with("/var/log/exim4/exim-mainlog-2012-10-10.gz").and_return([])
            MailServerLog.load_file("/var/log/exim4/exim-mainlog-2012-10-10.gz")
        end
    end

    describe ".email_addresses_on_line" do
        before :each do
            AlaveteliConfiguration.stub!(:incoming_email_domain).and_return("example.com")
            AlaveteliConfiguration.stub!(:incoming_email_prefix).and_return("foi+")
        end

        it "recognises a single incoming email" do
            MailServerLog.email_addresses_on_line("a random log line foi+request-14-e0e09f97@example.com has an email").should ==
                ["foi+request-14-e0e09f97@example.com"]            
        end

        it "recognises two email addresses on the same line" do
            MailServerLog.email_addresses_on_line("two email addresses here foi+request-10-1234@example.com and foi+request-14-e0e09f97@example.com").should ==
                ["foi+request-10-1234@example.com", "foi+request-14-e0e09f97@example.com"]
        end

        it "returns an empty array when there is an email address from a different domain" do
            MailServerLog.email_addresses_on_line("other foi+request-10-1234@foo.com").should be_empty
        end

        it "ignores an email with a different prefix" do
            MailServerLog.email_addresses_on_line("foitest+request-14-e0e09f97@example.com").should be_empty            
        end

        it "ignores an email where the . is substituted for something else" do
            MailServerLog.email_addresses_on_line("foi+request-14-e0e09f97@exampledcom").should be_empty
        end
    end

    context "Postfix" do
        let(:log) {[
"Oct  3 16:39:35 host postfix/pickup[2257]: CB55836EE58C: uid=1003 from=<foitest+request-14-e0e09f97@example.com>",
"Oct  3 16:39:35 host postfix/cleanup[7674]: CB55836EE58C: message-id=<ogm-15+506bdda7a4551-20ee@example.com>",
"Oct  3 16:39:35 host postfix/qmgr[1673]: 9634B16F7F7: from=<foitest+request-10-1234@example.com>, size=368, nrcpt=1 (queue active)",
"Oct  3 16:39:35 host postfix/qmgr[15615]: CB55836EE58C: from=<foitest+request-14-e0e09f97@example.com>, size=1695, nrcpt=1 (queue active)",
"Oct  3 16:39:38 host postfix/smtp[7676]: CB55836EE58C: to=<foi@some.gov.au>, relay=aspmx.l.google.com[74.125.25.27]:25, delay=2.5, delays=0.13/0.02/1.7/0.59, dsn=2.0.0, status=sent (250 2.0.0 OK 1349246383 j9si1676296paw.328)",
"Oct  3 16:39:38 host postfix/smtp[1681]: 9634B16F7F7: to=<kdent@example.com>, relay=none, delay=46, status=deferred (connect to 216.150.150.131[216.150.150.131]: No route to host)",
"Oct  3 16:39:38 host postfix/qmgr[15615]: CB55836EE58C: removed",
        ]}

        describe ".load_postfix_log_data" do
            # Postfix logs for a single email go over multiple lines. They are all tied together with the Queue ID.
            # See http://onlamp.com/onlamp/2004/01/22/postfix.html
            it "loads the postfix log and untangles seperate email transactions using the queue ID" do
                AlaveteliConfiguration.stub!(:incoming_email_domain).and_return("example.com")
                log.stub!(:rewind)
                ir1 = info_requests(:fancy_dog_request)
                ir2 = info_requests(:naughty_chicken_request)
                InfoRequest.should_receive(:find_by_incoming_email).with("request-14-e0e09f97@example.com").any_number_of_times.and_return(ir1)
                InfoRequest.should_receive(:find_by_incoming_email).with("request-10-1234@example.com").any_number_of_times.and_return(ir2)
                MailServerLog.load_postfix_log_data(log, MailServerLogDone.new(:filename => "foo", :last_stat => DateTime.now))
                # TODO: Check that each log line is attached to the correct request
                ir1.mail_server_logs.count.should == 5
                ir1.mail_server_logs[0].order.should == 1
                ir1.mail_server_logs[0].line.should == "Oct  3 16:39:35 host postfix/pickup[2257]: CB55836EE58C: uid=1003 from=<foitest+request-14-e0e09f97@example.com>"
                ir1.mail_server_logs[1].order.should == 2
                ir1.mail_server_logs[1].line.should == "Oct  3 16:39:35 host postfix/cleanup[7674]: CB55836EE58C: message-id=<ogm-15+506bdda7a4551-20ee@example.com>"
                ir1.mail_server_logs[2].order.should == 4
                ir1.mail_server_logs[2].line.should == "Oct  3 16:39:35 host postfix/qmgr[15615]: CB55836EE58C: from=<foitest+request-14-e0e09f97@example.com>, size=1695, nrcpt=1 (queue active)"
                ir1.mail_server_logs[3].order.should == 5
                ir1.mail_server_logs[3].line.should == "Oct  3 16:39:38 host postfix/smtp[7676]: CB55836EE58C: to=<foi@some.gov.au>, relay=aspmx.l.google.com[74.125.25.27]:25, delay=2.5, delays=0.13/0.02/1.7/0.59, dsn=2.0.0, status=sent (250 2.0.0 OK 1349246383 j9si1676296paw.328)"
                ir1.mail_server_logs[4].order.should == 7
                ir1.mail_server_logs[4].line.should == "Oct  3 16:39:38 host postfix/qmgr[15615]: CB55836EE58C: removed"
                ir2.mail_server_logs.count.should == 2
                ir2.mail_server_logs[0].order.should == 3
                ir2.mail_server_logs[0].line.should == "Oct  3 16:39:35 host postfix/qmgr[1673]: 9634B16F7F7: from=<foitest+request-10-1234@example.com>, size=368, nrcpt=1 (queue active)"
                ir2.mail_server_logs[1].order.should == 6
                ir2.mail_server_logs[1].line.should == "Oct  3 16:39:38 host postfix/smtp[1681]: 9634B16F7F7: to=<kdent@example.com>, relay=none, delay=46, status=deferred (connect to 216.150.150.131[216.150.150.131]: No route to host)"
            end
        end

        describe ".scan_for_postfix_queue_ids" do
            it "returns the queue ids of interest with the connected email addresses" do
                AlaveteliConfiguration.stub!(:incoming_email_domain).and_return("example.com")
                MailServerLog.scan_for_postfix_queue_ids(log).should == {
                    "CB55836EE58C" => ["request-14-e0e09f97@example.com"],
                    "9634B16F7F7" => ["request-10-1234@example.com"]
                }
            end
        end

        describe ".extract_postfix_queue_id_from_syslog_line" do
            it "returns nil if there is no queue id" do                
                MailServerLog.extract_postfix_queue_id_from_syslog_line("Oct  7 07:16:48 kedumba postfix/smtp[14294]: connect to mail.neilcopp.com.au[110.142.151.66]:25: Connection refused").should be_nil
            end
        end

        describe ".request_postfix_sent?" do
            it "returns true when the logs say the message was sent" do
                ir = info_requests(:fancy_dog_request)
                ir.mail_server_logs.create!(:line => "Oct 10 16:58:38 kedumba postfix/smtp[26358]: A664436F218D: to=<contact@openaustraliafoundation.org.au>, relay=aspmx.l.google.com[74.125.25.26]:25, delay=2.7, delays=0.16/0.02/1.8/0.67, dsn=2.0.0, status=sent (250 2.0.0 OK 1349848723 e6si653316paw.346)", :order => 1)
                MailServerLog.request_postfix_sent?(ir).should be_true
            end

            it "returns false when the logs say the message hasn't been sent" do
                ir = info_requests(:fancy_dog_request)
                ir.mail_server_logs.create!(:line => "Oct 10 13:22:49 kedumba postfix/smtp[11876]: 6FB9036F1307: to=<foo@example.com>, relay=mta7.am0.yahoodns.net[74.6.136.244]:25, delay=1.5, delays=0.03/0/0.48/1, dsn=5.0.0, status=bounced (host mta7.am0.yahoodns.net[74.6.136.244] said: 554 delivery error: dd Sorry your message to foo@example.com cannot be delivered. This account has been disabled or discontinued [#102]. - mta1272.mail.sk1.yahoo.com (in reply to end of DATA command))", :order => 1)
                MailServerLog.request_postfix_sent?(ir).should be_false
            end
        end
    end
end