Unit tests for SMTP.

This commit is contained in:
Vincent Richard 2006-05-05 20:50:26 +00:00
parent 4579f8f95a
commit 2791e5d41e
6 changed files with 745 additions and 3 deletions

View File

@ -333,6 +333,7 @@ libvmimetest_common = [
libvmimetest_sources = [
'tests/testRunner.cpp',
'tests/testUtils.cpp',
# ============================== Parser ==============================
'tests/parser/attachmentHelperTest.cpp',
'tests/parser/bodyPartTest.cpp',
@ -360,7 +361,10 @@ libvmimetest_sources = [
'tests/misc/importanceHelperTest.cpp',
# ============================= Security =============================
'tests/security/digest/md5Test.cpp',
'tests/security/digest/sha1Test.cpp'
'tests/security/digest/sha1Test.cpp',
# =============================== Net ================================
'tests/net/smtp/SMTPTransportTest.cpp',
'tests/net/smtp/SMTPResponseTest.cpp'
]
libvmime_autotools = [

View File

@ -0,0 +1,189 @@
//
// VMime library (http://www.vmime.org)
// Copyright (C) 2002-2006 Vincent Richard <vincent@vincent-richard.net>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of
// the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// Linking this library statically or dynamically with other modules is making
// a combined work based on this library. Thus, the terms and conditions of
// the GNU General Public License cover the whole combination.
//
#include "tests/testUtils.hpp"
#include "vmime/net/smtp/SMTPResponse.hpp"
#define VMIME_TEST_SUITE SMTPResponseTest
#define VMIME_TEST_SUITE_MODULE "Net/SMTP"
VMIME_TEST_SUITE_BEGIN
VMIME_TEST_LIST_BEGIN
VMIME_TEST(testSingleLineResponse)
VMIME_TEST(testSingleLineResponseLF)
VMIME_TEST(testMultiLineResponse)
VMIME_TEST(testMultiLineResponseDifferentCode)
VMIME_TEST(testIncompleteMultiLineResponse)
VMIME_TEST(testIntermediateResponse)
VMIME_TEST(testNoResponseText)
VMIME_TEST_LIST_END
void testSingleLineResponse()
{
vmime::ref <testSocket> socket = vmime::create <testSocket>();
vmime::ref <vmime::net::timeoutHandler> toh =
vmime::create <testTimeoutHandler>();
socket->localSend("123 Response Text\r\n");
vmime::ref <vmime::net::smtp::SMTPResponse> resp =
vmime::net::smtp::SMTPResponse::readResponse(socket, toh);
VASSERT_EQ("Code", 123, resp->getCode());
VASSERT_EQ("Lines", 1, resp->getLineCount());
VASSERT_EQ("Text", "Response Text", resp->getText());
}
void testSingleLineResponseLF()
{
vmime::ref <testSocket> socket = vmime::create <testSocket>();
vmime::ref <vmime::net::timeoutHandler> toh =
vmime::create <testTimeoutHandler>();
socket->localSend("123 Response Text\n");
vmime::ref <vmime::net::smtp::SMTPResponse> resp =
vmime::net::smtp::SMTPResponse::readResponse(socket, toh);
VASSERT_EQ("Code", 123, resp->getCode());
VASSERT_EQ("Lines", 1, resp->getLineCount());
VASSERT_EQ("Text", "Response Text", resp->getText());
}
void testMultiLineResponse()
{
vmime::ref <testSocket> socket = vmime::create <testSocket>();
vmime::ref <vmime::net::timeoutHandler> toh =
vmime::create <testTimeoutHandler>();
socket->localSend
(
"123-Response\r\n"
"123 Text\r\n"
);
vmime::ref <vmime::net::smtp::SMTPResponse> resp =
vmime::net::smtp::SMTPResponse::readResponse(socket, toh);
VASSERT_EQ("Code", 123, resp->getCode());
VASSERT_EQ("Lines", 2, resp->getLineCount());
VASSERT_EQ("Text", "Response\nText", resp->getText());
VASSERT_EQ("Code", 123, resp->getLineAt(0).getCode());
VASSERT_EQ("Text", "Response", resp->getLineAt(0).getText());
VASSERT_EQ("Code", 123, resp->getLineAt(1).getCode());
VASSERT_EQ("Text", "Text", resp->getLineAt(1).getText());
}
void testMultiLineResponseDifferentCode()
{
vmime::ref <testSocket> socket = vmime::create <testSocket>();
vmime::ref <vmime::net::timeoutHandler> toh =
vmime::create <testTimeoutHandler>();
socket->localSend
(
"123-Response\r\n"
"456 Text\r\n"
);
vmime::ref <vmime::net::smtp::SMTPResponse> resp =
vmime::net::smtp::SMTPResponse::readResponse(socket, toh);
VASSERT_EQ("Code", 0, resp->getCode());
VASSERT_EQ("Lines", 2, resp->getLineCount());
VASSERT_EQ("Text", "Response\nText", resp->getText());
VASSERT_EQ("Code", 123, resp->getLineAt(0).getCode());
VASSERT_EQ("Text", "Response", resp->getLineAt(0).getText());
VASSERT_EQ("Code", 456, resp->getLineAt(1).getCode());
VASSERT_EQ("Text", "Text", resp->getLineAt(1).getText());
}
void testIncompleteMultiLineResponse()
{
vmime::ref <testSocket> socket = vmime::create <testSocket>();
vmime::ref <vmime::net::timeoutHandler> toh =
vmime::create <testTimeoutHandler>(1);
socket->localSend
(
"123-Response\r\n"
"123-Text\r\n"
// Missing data
);
VASSERT_THROW("Incomplete response",
vmime::net::smtp::SMTPResponse::readResponse(socket, toh),
vmime::exceptions::operation_timed_out);
}
void testIntermediateResponse()
{
vmime::ref <testSocket> socket = vmime::create <testSocket>();
vmime::ref <vmime::net::timeoutHandler> toh =
vmime::create <testTimeoutHandler>(1);
socket->localSend
(
"334\r\n"
"More information\r\n"
);
vmime::ref <vmime::net::smtp::SMTPResponse> resp =
vmime::net::smtp::SMTPResponse::readResponse(socket, toh);
VASSERT_EQ("Code", 334, resp->getCode());
VASSERT_EQ("Lines", 1, resp->getLineCount());
VASSERT_EQ("Text", "More information", resp->getText());
}
void testNoResponseText()
{
vmime::ref <testSocket> socket = vmime::create <testSocket>();
vmime::ref <vmime::net::timeoutHandler> toh =
vmime::create <testTimeoutHandler>(1);
socket->localSend
(
"250\r\n"
);
vmime::ref <vmime::net::smtp::SMTPResponse> resp =
vmime::net::smtp::SMTPResponse::readResponse(socket, toh);
VASSERT_EQ("Code", 250, resp->getCode());
VASSERT_EQ("Lines", 1, resp->getLineCount());
VASSERT_EQ("Text", "", resp->getText());
}
VMIME_TEST_SUITE_END

View File

@ -0,0 +1,256 @@
//
// VMime library (http://www.vmime.org)
// Copyright (C) 2002-2006 Vincent Richard <vincent@vincent-richard.net>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of
// the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// Linking this library statically or dynamically with other modules is making
// a combined work based on this library. Thus, the terms and conditions of
// the GNU General Public License cover the whole combination.
//
#include "tests/testUtils.hpp"
#define VMIME_TEST_SUITE SMTPTransportTest
#define VMIME_TEST_SUITE_MODULE "Net/SMTP"
class greetingErrorSMTPTestSocket;
class MAILandRCPTSMTPTestSocket;
VMIME_TEST_SUITE_BEGIN
VMIME_TEST_LIST_BEGIN
VMIME_TEST(testGreetingError)
VMIME_TEST(testMAILandRCPT)
VMIME_TEST_LIST_END
void testGreetingError()
{
vmime::ref <vmime::net::session> session =
vmime::create <vmime::net::session>();
vmime::ref <vmime::net::transport> tr = session->getTransport
(vmime::utility::url("smtp://localhost"));
tr->setSocketFactory(vmime::create <testSocketFactory <greetingErrorSMTPTestSocket> >());
tr->setTimeoutHandlerFactory(vmime::create <testTimeoutHandlerFactory>());
VASSERT_THROW("Connection", tr->connect(),
vmime::exceptions::connection_greeting_error);
}
void testMAILandRCPT()
{
vmime::ref <vmime::net::session> session =
vmime::create <vmime::net::session>();
vmime::ref <vmime::net::transport> tr = session->getTransport
(vmime::utility::url("smtp://localhost"));
tr->setSocketFactory(vmime::create <testSocketFactory <MAILandRCPTSMTPTestSocket> >());
tr->setTimeoutHandlerFactory(vmime::create <testTimeoutHandlerFactory>());
VASSERT_NO_THROW("Connection", tr->connect());
vmime::mailbox exp("expeditor@test.vmime.org");
vmime::mailboxList recips;
recips.appendMailbox(vmime::create <vmime::mailbox>("recipient1@test.vmime.org"));
recips.appendMailbox(vmime::create <vmime::mailbox>("recipient2@test.vmime.org"));
recips.appendMailbox(vmime::create <vmime::mailbox>("recipient3@test.vmime.org"));
vmime::string data("Message data");
vmime::utility::inputStreamStringAdapter is(data);
tr->send(exp, recips, is, 0);
}
VMIME_TEST_SUITE_END
/** Accepts connection and fails on greeting.
*/
class greetingErrorSMTPTestSocket : public lineBasedTestSocket
{
public:
void onConnected()
{
localSend("421 test.vmime.org Service not available, closing transmission channel\r\n");
disconnect();
}
void processCommand()
{
if (!haveMoreLines())
return;
getNextLine();
localSend("502 Command not implemented\r\n");
processCommand();
}
};
/** SMTP test server 1.
*
* Test send().
* Ensure MAIL and RCPT commands are sent correctly.
*/
class MAILandRCPTSMTPTestSocket : public lineBasedTestSocket
{
public:
MAILandRCPTSMTPTestSocket()
{
m_recipients.insert("recipient1@test.vmime.org");
m_recipients.insert("recipient2@test.vmime.org");
m_recipients.insert("recipient3@test.vmime.org");
m_state = STATE_NOT_CONNECTED;
}
void onConnected()
{
localSend("220 test.vmime.org Service ready\r\n");
processCommand();
m_state = STATE_COMMAND;
}
void processCommand()
{
if (!haveMoreLines())
return;
vmime::string line = getNextLine();
std::istringstream iss(line);
switch (m_state)
{
case STATE_NOT_CONNECTED:
localSend("451 Requested action aborted: invalid state\r\n");
break;
case STATE_COMMAND:
{
std::string cmd;
iss >> cmd;
if (cmd.empty())
{
localSend("500 Syntax error, command unrecognized\r\n");
}
else if (cmd == "HELO")
{
localSend("250 OK\r\n");
}
else if (cmd == "MAIL")
{
VASSERT_EQ("MAIL", std::string("MAIL FROM: <expeditor@test.vmime.org>"), line);
localSend("250 OK\r\n");
}
else if (cmd == "RCPT")
{
const vmime::string::size_type lt = line.find('<');
const vmime::string::size_type gt = line.find('>');
VASSERT("RCPT <", lt != vmime::string::npos);
VASSERT("RCPT >", gt != vmime::string::npos);
VASSERT("RCPT ><", gt >= lt);
const vmime::string recip = vmime::string
(line.begin() + lt + 1, line.begin() + gt);
std::set <vmime::string>::iterator it =
m_recipients.find(recip);
VASSERT(std::string("Recipient not found: '") + recip + "'",
it != m_recipients.end());
m_recipients.erase(it);
localSend("250 OK, recipient accepted\r\n");
}
else if (cmd == "DATA")
{
VASSERT("All recipients", m_recipients.empty());
localSend("354 Ready to accept data; end with <CRLF>.<CRLF>\r\n");
m_state = STATE_DATA;
m_msgData.clear();
}
else if (cmd == "NOOP")
{
localSend("250 Completed\r\n");
}
else if (cmd == "QUIT")
{
localSend("221 test.vmime.org Service closing transmission channel\r\n");
}
else
{
localSend("502 Command not implemented\r\n");
}
break;
}
case STATE_DATA:
{
if (line == ".")
{
VASSERT_EQ("Data", "Message data\r\n", m_msgData);
localSend("250 Message accepted for delivery\r\n");
m_state = STATE_COMMAND;
}
else
{
m_msgData += line + "\r\n";
}
break;
}
}
processCommand();
}
private:
enum State
{
STATE_NOT_CONNECTED,
STATE_COMMAND,
STATE_DATA
};
int m_state;
std::set <vmime::string> m_recipients;
std::string m_msgData;
};

View File

@ -210,7 +210,7 @@ int main(int argc, char* argv[])
// Run the tests
if (xmlOutput)
{
// Get the test suites from the registry and add them to the list of test to run
// Get the test suites from the registry and add them to the list of tests to run
CppUnit::TestRunner runner;
for (unsigned int i = 0 ; i < getTestModules().size() ; ++i)
@ -231,7 +231,7 @@ int main(int argc, char* argv[])
xmlListener->output(std::cout);
// Return error code 1 if the one of test failed
// Return error code 1 if a test failed
return result.wasSuccessful() ? 0 : 1;
}
else

183
tests/testUtils.cpp Normal file
View File

@ -0,0 +1,183 @@
//
// VMime library (http://www.vmime.org)
// Copyright (C) 2002-2005 Vincent Richard <vincent@vincent-richard.net>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of
// the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// Linking this library statically or dynamically with other modules is making
// a combined work based on this library. Thus, the terms and conditions of
// the GNU General Public License cover the whole combination.
//
#include "testUtils.hpp"
// testSocket
void testSocket::connect(const vmime::string& address, const vmime::port_t port)
{
m_address = address;
m_port = port;
m_connected = true;
onConnected();
}
void testSocket::disconnect()
{
m_address.clear();
m_port = 0;
m_connected = false;
}
const bool testSocket::isConnected() const
{
return m_connected;
}
void testSocket::receive(vmime::string& buffer)
{
buffer = m_inBuffer;
m_inBuffer.clear();
}
void testSocket::send(const vmime::string& buffer)
{
m_outBuffer += buffer;
onDataReceived();
}
const int testSocket::receiveRaw(char* buffer, const int count)
{
const int n = std::min(count, static_cast <int>(m_inBuffer.size()));
std::copy(m_inBuffer.begin(), m_inBuffer.begin() + n, buffer);
m_inBuffer.erase(m_inBuffer.begin(), m_inBuffer.begin() + n);
return n;
}
void testSocket::sendRaw(const char* buffer, const int count)
{
send(vmime::string(buffer, count));
}
void testSocket::localSend(const vmime::string& buffer)
{
m_inBuffer += buffer;
}
void testSocket::localReceive(vmime::string& buffer)
{
buffer = m_outBuffer;
m_outBuffer.clear();
}
void testSocket::onDataReceived()
{
// Override
}
void testSocket::onConnected()
{
// Override
}
// lineBasedTestSocket
void lineBasedTestSocket::onDataReceived()
{
vmime::string chunk;
localReceive(chunk);
m_buffer += chunk;
vmime::string::size_type eol;
while ((eol = m_buffer.find('\n')) != vmime::string::npos)
{
vmime::string line(std::string(m_buffer.begin(), m_buffer.begin() + eol));
if (!line.empty() && line[line.length() - 1] == '\r')
line.erase(line.end() - 1, line.end());
m_lines.push_back(line);
m_buffer.erase(m_buffer.begin(), m_buffer.begin() + eol + 1);
}
while (!m_lines.empty())
processCommand();
}
const vmime::string lineBasedTestSocket::getNextLine()
{
const vmime::string line = m_lines.front();
m_lines.erase(m_lines.begin(), m_lines.begin() + 1);
return line;
}
const bool lineBasedTestSocket::haveMoreLines() const
{
return !m_lines.empty();
}
// testTimeoutHandler
testTimeoutHandler::testTimeoutHandler(const unsigned int delay)
: m_delay(delay), m_start(0)
{
}
const bool testTimeoutHandler::isTimeOut()
{
return (vmime::platformDependant::getHandler()->getUnixTime() - m_start) >= m_delay;
}
void testTimeoutHandler::resetTimeOut()
{
m_start = vmime::platformDependant::getHandler()->getUnixTime();
}
const bool testTimeoutHandler::handleTimeOut()
{
return false;
}
// testTimeoutHandlerFactory : public vmime::net::timeoutHandlerFactory
vmime::ref <vmime::net::timeoutHandler> testTimeoutHandlerFactory::create()
{
return vmime::create <testTimeoutHandler>();
}

View File

@ -193,3 +193,113 @@ inline std::ostream& operator<<(std::ostream& os, const vmime::datetime& d)
}
// Used to test network features.
//
// This works like a local pipe: client reads and writes data using receive()
// and send(). Server reads incoming data with localReceive() and sends data
// to client with localSend().
class testSocket : public vmime::net::socket
{
public:
void connect(const vmime::string& address, const vmime::port_t port);
void disconnect();
const bool isConnected() const;
void receive(vmime::string& buffer);
void send(const vmime::string& buffer);
const int receiveRaw(char* buffer, const int count);
void sendRaw(const char* buffer, const int count);
/** Send data to client.
*
* @buffer data to send
*/
void localSend(const vmime::string& buffer);
/** Receive data from client.
*
* @buffer buffer in which to store received data
*/
void localReceive(vmime::string& buffer);
protected:
/** Called when the client has sent some data.
*/
virtual void onDataReceived();
/** Called when the client has connected.
*/
virtual void onConnected();
private:
vmime::string m_address;
vmime::port_t m_port;
bool m_connected;
vmime::string m_inBuffer;
vmime::string m_outBuffer;
};
template <typename T>
class testSocketFactory : public vmime::net::socketFactory
{
public:
vmime::ref <vmime::net::socket> create()
{
return vmime::create <T>();
}
};
class lineBasedTestSocket : public testSocket
{
public:
void onDataReceived();
const vmime::string getNextLine();
const bool haveMoreLines() const;
virtual void processCommand() = 0;
private:
std::vector <vmime::string> m_lines;
std::string m_buffer;
};
class testTimeoutHandler : public vmime::net::timeoutHandler
{
public:
testTimeoutHandler(const unsigned int delay = 3);
const bool isTimeOut();
void resetTimeOut();
const bool handleTimeOut();
private:
unsigned int m_delay;
unsigned int m_start;
};
class testTimeoutHandlerFactory : public vmime::net::timeoutHandlerFactory
{
public:
vmime::ref <vmime::net::timeoutHandler> create();
};