unit test_Autosaves;

{$mode objfpc}{$H+}

/// <summary>
/// FPCUnit tests for uHCLAutosaves: iterative autosave names, grouping by
/// base newest first (by file time, since the GE restarts its counter every
/// run), the keep-newest and older-than selections, matching a base to an
/// installed scenario, and removing (permanently here; the Recycle Bin is
/// Windows shell behaviour) or moving the files.
/// </summary>

interface

uses
  Classes, SysUtils, fpcunit, testregistry, uHCLFileOrganization, uHCLAutosaves;

type
  TTestAutosaves = class(TTestCase)
  private
    FDir: String;
    FSaves: String;
    procedure MakeSave(const AName: String; AAgeDays: Double; ASize: Integer = 100);
  protected
    procedure SetUp; override;
    procedure TearDown; override;
  published
    procedure TestParseName;
    procedure TestParseRefusals;
    /// <summary>Groups by base, newest first by file time; other files ignored.</summary>
    procedure TestScan;
    /// <summary>A later run overwriting 0001 makes it the newest, whatever its number.</summary>
    procedure TestNewestIsByTimeNotNumber;
    procedure TestKeepNewest;
    procedure TestOlderThan;
    procedure TestBuiltinBase;
    /// <summary>A base matches the installed scenario whose file has that name.</summary>
    procedure TestFindScenarioForBase;
    procedure TestRemove;
    procedure TestMove;
  end;

implementation

procedure TTestAutosaves.SetUp;
begin
  FDir := IncludeTrailingPathDelimiter(GetTempDir) + 'HCLAutosaves_' + IntToStr(GetTickCount64);
  FSaves := FDir + PathDelim + HCL_ITER_SAVE_DIR;
  ForceDirectories(FSaves);
end;

procedure TTestAutosaves.TearDown;

  procedure Wipe(const ADir: String);
  var
    SR: TSearchRec;
  begin
    if FindFirst(ADir + PathDelim + '*', faAnyFile, SR) = 0 then
    try
      repeat
        if (SR.Name = '.') or (SR.Name = '..') then
          Continue;
        if (SR.Attr and faDirectory) <> 0 then
          Wipe(ADir + PathDelim + SR.Name)
        else
          DeleteFile(ADir + PathDelim + SR.Name);
      until FindNext(SR) <> 0;
    finally
      FindClose(SR);
    end;
    RemoveDir(ADir);
  end;

begin
  Wipe(FDir);
end;

procedure TTestAutosaves.MakeSave(const AName: String; AAgeDays: Double; ASize: Integer);
var
  FS: TFileStream;
  Buf: array of Byte;
begin
  FS := TFileStream.Create(FSaves + PathDelim + AName, fmCreate);
  try
    SetLength(Buf, ASize);
    if ASize > 0 then
      FS.WriteBuffer(Buf[0], ASize);
  finally
    FS.Free;
  end;
  FileSetDate(FSaves + PathDelim + AName, DateTimeToFileDate(Now - AAgeDays));
end;

procedure TTestAutosaves.TestParseName;
var
  Base: String;
  N: Integer;
begin
  AssertTrue(HCLParseAutosaveName('026-000.0017.hpq', Base, N));
  AssertEquals('built-in base', '026-000', Base);
  AssertEquals('counter', 17, N);
  AssertTrue(HCLParseAutosaveName('PINGER.0001.hpm', Base, N));
  AssertEquals('user scenario base', 'PINGER', Base);
  AssertTrue(HCLParseAutosaveName('My.Scenario.v2.4096.hp1', Base, N));
  AssertEquals('dots in the base', 'My.Scenario.v2', Base);
  AssertEquals('highest counter', 4096, N);
  AssertTrue(HCLParseAutosaveName('GIUK.0003.USNI-hp1', Base, N));
  AssertEquals('USNI extension', 'GIUK', Base);
end;

procedure TTestAutosaves.TestParseRefusals;
var
  Base: String;
  N: Integer;
begin
  AssertFalse('the plain autosave', HCLParseAutosaveName('autosave.hpq', Base, N));
  AssertFalse('three digits', HCLParseAutosaveName('026-000.017.hpq', Base, N));
  AssertFalse('five digits', HCLParseAutosaveName('026-000.00017.hpq', Base, N));
  AssertFalse('not a save', HCLParseAutosaveName('026-000.0017.txt', Base, N));
  AssertFalse('no base', HCLParseAutosaveName('.0017.hpq', Base, N));
  AssertFalse('letters', HCLParseAutosaveName('x.00a7.hpq', Base, N));
end;

procedure TTestAutosaves.TestScan;
var
  G: THCLAutosaveGroupArray;
begin
  MakeSave('026-001.0001.hpq', 3, 200);
  MakeSave('026-000.0001.hpq', 10);
  MakeSave('026-000.0002.hpq', 9);
  MakeSave('026-000.0003.hpq', 8);
  MakeSave('notes.txt', 1);
  MakeSave('autosave.hpq', 1);
  G := HCLScanAutosaves(FSaves);
  AssertEquals('two bases', 2, Length(G));
  AssertEquals('sorted by base', '026-000', G[0].Base);
  AssertEquals('three files', 3, Length(G[0].Files));
  AssertEquals('newest first', '026-000.0003.hpq', G[0].Files[0].Name);
  AssertEquals('oldest last', '026-000.0001.hpq', G[0].Files[2].Name);
  AssertEquals('total size', 300, G[0].TotalSize);
  AssertTrue('newest time', G[0].Newest > G[0].Oldest);
  AssertEquals('other base', 200, G[1].TotalSize);
  AssertEquals('missing folder', 0, Length(HCLScanAutosaves(FDir + PathDelim + 'nope')));
end;

procedure TTestAutosaves.TestNewestIsByTimeNotNumber;
var
  G: THCLAutosaveGroupArray;
begin
  MakeSave('PINGER.0040.hpm', 5);   // end of an earlier run
  MakeSave('PINGER.0001.hpm', 0.1); // a later run starts again at 0001
  G := HCLScanAutosaves(FSaves);
  AssertEquals('the later run''s save is newest', 'PINGER.0001.hpm', G[0].Files[0].Name);
end;

procedure TTestAutosaves.TestKeepNewest;
var
  G: THCLAutosaveGroupArray;
  Del: THCLAutosaveArray;
  I: Integer;
begin
  for I := 1 to 5 do
    MakeSave(Format('026-000.%.4d.hpq', [I]), 10 - I);
  G := HCLScanAutosaves(FSaves);
  Del := HCLKeepNewest(G[0], 2);
  AssertEquals('three to go', 3, Length(Del));
  AssertEquals('newest of those', '026-000.0003.hpq', Del[0].Name);
  AssertEquals('keep 0: all', 5, Length(HCLKeepNewest(G[0], 0)));
  AssertEquals('keep more than there are: none', 0, Length(HCLKeepNewest(G[0], 9)));
  AssertEquals('bytes', 300, HCLAutosaveBytes(Del));
end;

procedure TTestAutosaves.TestOlderThan;
var
  G: THCLAutosaveGroupArray;
  Old: THCLAutosaveArray;
begin
  MakeSave('026-000.0001.hpq', 40);
  MakeSave('026-000.0002.hpq', 31);
  MakeSave('026-000.0003.hpq', 2);
  G := HCLScanAutosaves(FSaves);
  Old := HCLOlderThan(G[0], 30, Now);
  AssertEquals('two older than 30 days', 2, Length(Old));
  AssertEquals('none older than 100', 0, Length(HCLOlderThan(G[0], 100, Now)));
end;

procedure TTestAutosaves.TestBuiltinBase;
var
  BS, Scen: Integer;
begin
  AssertTrue(HCLParseBuiltinBase('026-000', BS, Scen));
  AssertEquals('battleset', 26, BS);
  AssertEquals('scenario, counted from 1', 1, Scen);
  AssertTrue(HCLParseBuiltinBase('120-004', BS, Scen));
  AssertEquals('USNI', 120, BS);
  AssertFalse('user scenario', HCLParseBuiltinBase('PINGER', BS, Scen));
  AssertFalse('shape', HCLParseBuiltinBase('26-000', BS, Scen));
end;

procedure TTestAutosaves.TestFindScenarioForBase;
var
  Org: IHCLFileOrganization;
  ScenDir: String;
  FS: TFileStream;
begin
  Org := CreateFileOrganization(FDir);
  ScenDir := Org.GetPlatformDBScenarioPath('HCDB-070414', 'Pinger Charlie');
  ForceDirectories(ScenDir);
  FS := TFileStream.Create(ScenDir + PathDelim + 'PINGER.SCm', fmCreate);
  FS.Free;
  AssertEquals('found, case-insensitive', ExcludeTrailingPathDelimiter(ScenDir),
    ExcludeTrailingPathDelimiter(HCLFindScenarioForBase(Org, 'pinger')));
  AssertEquals('no such scenario', '', HCLFindScenarioForBase(Org, '026-000'));
  AssertEquals('no tree', '', HCLFindScenarioForBase(nil, 'PINGER'));
end;

procedure TTestAutosaves.TestRemove;
var
  G: THCLAutosaveGroupArray;
  Err: String;
begin
  MakeSave('026-000.0001.hpq', 2);
  MakeSave('026-000.0002.hpq', 1);
  G := HCLScanAutosaves(FSaves);
  AssertEquals('both removed', 2, HCLRemoveAutosaves(G[0].Files, False, Err));
  AssertEquals('no error', '', Err);
  AssertEquals('gone', 0, Length(HCLScanAutosaves(FSaves)));
  AssertEquals('already gone: counted as failures', 0, HCLRemoveAutosaves(G[0].Files, False, Err));
  AssertTrue('and reported', Err <> '');
end;

procedure TTestAutosaves.TestMove;
var
  G: THCLAutosaveGroupArray;
  Dest, Err: String;
  T: TDateTime;
begin
  MakeSave('PINGER.0001.hpm', 3);
  MakeSave('PINGER.0002.hpm', 2);
  G := HCLScanAutosaves(FSaves);
  T := G[0].Files[0].Time;
  Dest := FDir + PathDelim + 'Scen' + PathDelim + 'SavedGames' + PathDelim + 'AutoSaves';
  AssertEquals('both moved', 2, HCLMoveAutosaves(G[0].Files, Dest, Err));
  AssertEquals('no error', '', Err);
  AssertTrue('arrived', FileExists(Dest + PathDelim + 'PINGER.0002.hpm'));
  AssertFalse('left the GE folder', FileExists(FSaves + PathDelim + 'PINGER.0002.hpm'));
  AssertEquals('time kept', Round(T * 86400), Round(FileDateToDateTime(FileAge(Dest + PathDelim + 'PINGER.0002.hpm')) * 86400));
end;

initialization
  RegisterTest(TTestAutosaves);

end.
