unit IdTestFtpServer;

//todo test commands return 'not logged in' error correctly

interface

uses
  IdGlobal,
  IdSys,
  IdObjs,
  IdTest,
  IdTcpClient,
  IdFTPListOutput,
  IdIOHandlerStack,
  IdLogDebug,
  IdFtp,
  IdThreadSafe,
  IdFtpServer;

type

  TIdTestFtpServer = class(TIdTest)
  private
    FDirectory:TIdThreadSafeString;
    procedure CallbackRetrieve(ASender: TIdFTPServerContext; const AFileName: string; var VStream: TIdStream);
    procedure CallbackStore(ASender: TIdFTPServerContext; const AFileName: string; AAppend: Boolean; var VStream: TIdStream);
    procedure CallbackListDirectory(ASender: TIdFTPServerContext; const APath: string;
      ADirectoryListing: TIdFTPListOutput; const ACmd : String; const ASwitches : string);
    procedure CallbackChangeDirectory(ASender: TIdFTPServerContext; var VDirectory: string);
  protected
    procedure Setup;override;
    procedure TearDown;override;
  published
    procedure TestBasic;
    procedure TestMethods;
    procedure TestBasic2;
  end;

  TIdTestStream = class(TIdMemoryStream)
  public
    destructor Destroy;override;
  end;

implementation

const
  cGreeting='HELLO';
  cTestFtpPort=20021;
  cContent='MYCONTENT';
  cPathOk='okpath';
  cPathError='errorpath';
  cUploadTo='file.txt';
  cGoodFilename='good.txt';
  cUnknownFilename='unknown.txt';
  cErrorFilename='error.txt';

procedure TIdTestFtpServer.CallbackChangeDirectory(
  ASender: TIdFTPServerContext; var VDirectory: string);
begin
  FDirectory.Value:=VDirectory;
end;

procedure TIdTestFtpServer.CallbackListDirectory(
  ASender: TIdFTPServerContext; const APath: string;
  ADirectoryListing: TIdFTPListOutput; const ACmd, ASwitches: string);
var
  aItem:TIdFTPListOutputItem;
begin
  if APath=cPathError then
    begin
    end
  else if APath=cPathOk then
    begin
    aItem:=ADirectoryListing.Add;
    aItem.FileName:='file1.txt';
    //int64 filesize
    aItem.Size:=5000000000;
    aItem.ModifiedDate:=Sys.Now;
    end;
end;

procedure TIdTestFtpServer.CallbackRetrieve(ASender: TIdFTPServerContext;
  const AFileName: string; var VStream: TIdStream);
begin
 if AFileName=cGoodFilename then
   begin
   VStream:=TIdStringStream.Create(cContent);
   end
 else if AFileName=cErrorFilename then
   begin
   Assert(False);
   end
 else if AFileName=cUnknownFilename then
   begin
   end;
end;

procedure TIdTestFtpServer.CallbackStore(ASender: TIdFTPServerContext;
  const AFileName: string; AAppend: Boolean; var VStream: TIdStream);
//var
//  s:string;
begin
  Assert(VStream=nil);
  if AFileName=cUploadTo then
   begin
   VStream:=TIdTestStream.Create;
   //s:=ReadStringFromStream(VStream);
   //Assert(s=cContent);
   end;
end;

procedure TIdTestFtpServer.Setup;
begin
  inherited;
  FDirectory:=TIdThreadSafeString.Create;
end;

procedure TIdTestFtpServer.TearDown;
begin
  Sys.FreeAndNil(FDirectory);
  inherited;
end;

procedure TIdTestFtpServer.TestBasic;
var
  s:TIdFTPServer;
  c:TIdTCPClient;
  aStr:string;
begin
  s:=TIdFTPServer.Create(nil);
  c:=TIdTCPClient.Create(nil);
  try
    s.Greeting.Text.Text:=cGreeting;
    s.DefaultPort:=cTestFtpPort;
    s.AllowAnonymousLogin:=True;
    s.OnListDirectory:=CallbackListDirectory;
    s.Active:=True;

    c.Port:=cTestFtpPort;
    c.Host:='127.0.0.1';
    c.CreateIOHandler;
    c.ReadTimeout:=500;
    c.Connect;

    //expect a greeting. typical="220 FTP Server Ready."
    aStr:=c.IOHandler.Readln;
    Assert(aStr = '220 ' + cGreeting, cGreeting);

    //ftp server should only process a command after crlf
    //see TIdFTPServer.ReadCommandLine
    c.IOHandler.Write('U');
    aStr:=c.IOHandler.Readln;
    Assert(aStr='',aStr);

    //complete the rest of the command
    c.IOHandler.WriteLn('SER ANONYMOUS');
    aStr:=c.IOHandler.Readln;
    Assert(aStr<>'',aStr);

    c.IOHandler.WriteLn('PASS a@b.com');
    aStr:=c.IOHandler.Readln;
    Assert(aStr<>'',aStr);

    //attempt to start a transfer when no datachannel setup.
    //should give 550 error?
    //typical quit='221 Goodbye.'

    //test commands that aren't currently in the ftp client?
    //nlst needs a data connection.
    //calling without causes an AV and memleak. should be fixed
    //c.IOHandler.WriteLn('NLST');
    //aStr:=c.IOHandler.Readln;
    //Assert(aStr<>'',aStr);
  finally
    Sys.FreeAndNil(c);
    Sys.FreeAndNil(s);
  end;
end;


procedure TIdTestFtpServer.TestBasic2;
var
  s:TIdFTPServer;
  c:TIdFTP;
  //aStr:string;
  aList:TIdStringList;
begin
  s:=TIdFTPServer.Create;
  c:=TIdFTP.Create;
  try
    s.Greeting.Text.Text:=cGreeting;
    s.DefaultPort:=cTestFtpPort;
    s.AllowAnonymousLogin:=True;
    s.Greeting.Text.Text:=cGreeting;
    s.OnListDirectory:=Self.CallbackListDirectory;
    s.OnChangeDirectory:=Self.CallbackChangeDirectory;
    s.Active:=True;

    c.Port:=cTestFtpPort;
    c.Host:='127.0.0.1';
    c.Username:='anonymous';
    c.Password:='a@b.com';
    c.Connect;

    Assert(c.Greeting.Text.Text=cGreeting+EOL);

    aList:=TIdStringList.Create;
    try
    c.List(aList);
    Assert(aList.Count>0);

    c.ChangeDir(cPathError);

    aList.Clear;
    c.List(aList);
    finally
    Sys.FreeAndNil(aList);
    end;

  finally
    Sys.FreeAndNil(c);
    Sys.FreeAndNil(s);
  end;
end;

procedure TIdTestFtpServer.TestMethods;
var
  s:TIdFTPServer;
  c:TIdFTP;
  aStream:TIdMemoryStream;
begin
  s:=TIdFTPServer.Create(nil);
  c:=TIdFTP.Create(nil);
  try
    s.Greeting.Text.Text:=cGreeting;
    s.DefaultPort:=cTestFtpPort;
    s.OnStoreFile:=CallbackStore;
    s.OnRetrieveFile:=CallbackRetrieve;
    s.Active:=True;
    c.Port:=cTestFtpPort;
    c.Host:='127.0.0.1';
    c.CreateIOHandler;
    c.IOHandler.ReadTimeout:=1000;
    c.AutoLogin:=False;
    c.Connect;

    //check invalid login
    //check valid login
    //check allow/disallow anonymous login

    s.AllowAnonymousLogin:=True;
    c.Username:='anonymous';
    c.Password:='bob@example.com';
    c.Login;

    repeat
    //check stream upload
    aStream:=TIdMemoryStream.Create;
    try
      WriteStringToStream(aStream, cContent);
      aStream.Position := 0;
      c.Put(aStream,cUploadTo);
    finally
      Sys.FreeAndNil(aStream);
    end;

    //check no dest filename
    //check missing source file
    //check file upload rejected by server. eg out of space?

    //check normal file upload. create a temp file? use c:\?
    //c.Put('c:\test.txt',cUploadTo);

    //test resume
    //test download unknown file

    aStream:=TIdMemoryStream.Create;
    try
    //test download to stream
    c.Get(cGoodFilename,aStream);
    //Assert(aStream.DataString=cContent);

    //test exception on server gets sent to client
{    aStream.Size:=0;
    try
    c.Get(cUnknownFilename,aStream);
    Assert(False);
    except
    //expect to be here
    end;
}
    finally
    Sys.FreeAndNil(aStream);
    end;
      if c.Passive then
      begin
        break;
      end;

//OutputLn('PASV Mode tests');
      c.Passive := True;
    until False;
  finally
    Sys.FreeAndNil(c);
    Sys.FreeAndNil(s);
  end;
end;

destructor TIdTestStream.Destroy;
begin
  inherited;
end;

initialization

  TIdTest.RegisterTest(TIdTestFtpServer);

end.
