unit IdTestSMTPServer;

//odd: TIdCommandHandler.DoCommand: Response.Assign(Self.Response);

interface

uses
  IdGlobal,
  IdReplySMTP,
  IdTCPClient,
  IdSMTPServer,
  IdMessage,
  IdObjs,
  IdSMTP,
  IdSys,
  IdLogDebug,
  IdTest;

type

  TIdTestSMTPServer = class(TIdTest)
  private
    FReceivedMsg:TIdMessage;
    FServer:TIdSMTPServer;
    FClient:TIdSMTP;
    FDebug:TIdLogDebug;
    //replace setup/teardown with virtuals
    procedure mySetup;
    procedure myTearDown;
    procedure CallbackRcptTo(ASender: TIdSMTPServerContext; const AAddress : string;
      var VAction : TIdRCPToReply; var VForward : string);
    procedure CallbackMsgReceive(ASender: TIdSMTPServerContext; AMsg : TIdStream;var LAction : TIdDataReply);
    procedure CallbackMailFrom(ASender: TIdSMTPServerContext; const AAddress : string;
      var VAction : TIdMailFromReply);
  published
    procedure TestGreeting;
    procedure TestReceive;
    procedure TestReject;
  end;

implementation

const
  cTestPort=20202;
  cSpammerAddress='spammer@example.com';

procedure TIdTestSMTPServer.mySetup;
begin
  Assert(FReceivedMsg=nil);
  FReceivedMsg:=TIdMessage.Create(nil);

  Assert(FServer=nil);
  FServer:=TIdSMTPServer.Create(nil);
  FServer.OnMsgReceive:=CallbackMsgReceive;
  FServer.OnRcptTo:=CallbackRcptTo;
  FServer.OnMailFrom:=CallbackMailFrom;
  FServer.DefaultPort:=cTestPort;

  FDebug:=TIdLogDebug.Create;

  Assert(FClient=nil);
  FClient:=TIdSMTP.Create(nil);
  FClient.ReadTimeout:=5000;
  FClient.Port:=cTestPort;
  FClient.Host:='127.0.0.1';
  FClient.CreateIOHandler;
  FClient.IOHandler.Intercept := FDebug;
  FDebug.Active := True;
end;

procedure TIdTestSMTPServer.myTearDown;
begin
  FDebug.Active := False;
  Sys.FreeAndNil(FDebug);
  Sys.FreeAndNil(FClient);
  Sys.FreeAndNil(FServer);
  Sys.FreeAndNil(FReceivedMsg);
end;

procedure TIdTestSMTPServer.CallbackMailFrom(ASender: TIdSMTPServerContext;
  const AAddress: string; var VAction: TIdMailFromReply);
begin
  if AAddress=cSpammerAddress then
  begin
    VAction:=mReject;
  end;
end;

procedure TIdTestSMTPServer.CallbackMsgReceive(
  ASender: TIdSMTPServerContext; AMsg: TIdStream;
  var LAction: TIdDataReply);
var
  aList:TIdStringList;
begin
  //do a precheck here.
  aList:=TIdStringList.Create;
  try
    AMsg.Position:=0;
    aList.Text:=ReadStringFromStream(AMsg);
    //should be at least headers for: received from subject to date
    //if this fails, then client hasn't written correctly, or server hasn't read
    Assert(aList.Count>=5);
  finally
    sys.FreeAndNil(aList);
  end;

  AMsg.Position:=0;
  FReceivedMsg.LoadFromStream(AMsg);
end;

procedure TIdTestSMTPServer.CallbackRcptTo(ASender: TIdSMTPServerContext;
  const AAddress: string; var VAction: TIdRCPToReply;
  var VForward: string);
begin
  VAction:=rAddressOk;
end;

procedure TIdTestSMTPServer.TestGreeting;
//checks that the server returns a correct greeting
//uses tcpclient to check directly
var
  s:string;
  aClient:TIdTCPClient;
const
  cGreeting='HELLO';
begin
  aClient:=TIdTCPClient.Create(nil);
  try
    mySetup;

    //Set a specific greeting, makes easy to test
    (FServer.Greeting as TIdReplySMTP).SetEnhReply(220, '' ,cGreeting);
    FServer.Active:=True;

    aClient.Port:=cTestPort;
    aClient.Host:='127.0.0.1';
    aClient.ReadTimeout:=5000;
    aClient.Connect;

    s:=aClient.IOHandler.Readln;
    //real example '220 mail.example.com ESMTP'
    Assert(s='220 '+cGreeting);
    aClient.Disconnect;
  finally
    Sys.FreeAndNil(aClient);
    myTearDown;
  end;
end;

procedure TIdTestSMTPServer.TestReceive;
//do a round-trip message send using smtp client and server
//repeat with/without client pipelining?
var
  aMessage:TIdMessage;
const
  cFrom='bob@example.com';
  cSubject='mysubject';
  cBody='1,2,3';
  cAddress='mary@example.com';
  cPriority:TIdMessagePriority=mpHigh;
begin
  try
    mySetup;

    FServer.Active:=True;
    FClient.Connect;

    aMessage := TIdMessage.Create(nil);
    try
      aMessage.From.Address := cFrom;
      aMessage.Subject := cSubject;
      aMessage.Body.CommaText := cBody;
      //test with multiple recipients
      aMessage.Recipients.Add.Address := cAddress;
      aMessage.Priority :=cPriority;
      aMessage.Encoding := mePlaintext;
      FClient.Send(aMessage);
    finally
      Sys.FreeAndNil(aMessage);
    end;

    Assert(FClient.LastCmdResult.NumericCode =250, 'Code:' + Sys.IntToStr(FClient.LastCmdResult.NumericCode));

    //check that what the server received is same as sent
    Assert(FReceivedMsg.From.Address = cFrom, 'From:' + FReceivedMsg.From.Address);
    Assert(FReceivedMsg.Subject = cSubject, 'Subject:' + FReceivedMsg.Subject);
    Assert(FReceivedMsg.Body.CommaText = cBody, 'Body:' + FReceivedMsg.Body.CommaText);
    Assert(FReceivedMsg.Recipients.EMailAddresses = cAddress, 'To:' + FReceivedMsg.Recipients.EMailAddresses);
    Assert(FReceivedMsg.Priority = cPriority, 'Priority');
    //also check the "Received:" header
    //also check attachments

    //currently breaks due to ioHandler rev26
  finally
    myTearDown;
  end;
end;

procedure TIdTestSMTPServer.TestReject;
//check that if a message is rejected by server, (here using OnMailFrom)
//the correct status is returned.
var
  aMessage:TIdMessage;
begin

  try
    mySetup;

    FServer.Active:=True;
    FClient.Connect;

    aMessage:=TIdMessage.Create(nil);
    try
      aMessage.From.Address:=cSpammerAddress;
      aMessage.Subject:='spam';
      aMessage.Body.CommaText:='spam';
      aMessage.Recipients.Add.Address:='bob@example.com';
      try
        FClient.Send(aMessage);
      except
        //want to ignore the exception here
        //EIdSMTPReplyError
        //check class,content
        on E : Exception do
        begin
          //You can NOT do:
          //
          //Assert(FClient.LastCmdResult.NumericCode = 550, FClient.LastCmdResult.FormattedReply.Text);
          //
          //because the exception may be from a line in a pipelined sequence and
          //LastCmdResult probably contains the result from the last command in the pipelined
          //sequence
          if E is EIdSMTPReplyError then
          begin
            with (E as EIdSMTPReplyError) do
            begin
              Assert( (E as EIdSMTPReplyError).ErrorCode = 550, (E as EIdSMTPReplyError).Message);
            end;
          end;
        end;
      end;
    finally
      Sys.FreeAndNil(aMessage);
    end;

  finally
    myTearDown;
  end;
end;

initialization

  TIdTest.RegisterTest(TIdTestSMTPServer);

end.
