unit TestInsightProtocol;

{$mode ObjFPC}{$H+}

interface

uses
  Classes, SysUtils, fpJSON;

Type
  ETestInsight = class(Exception);

  TTestResultType = (rtUnknown, rtPassed, rtFailed, rtError, rtWarning, rtSkipped, rtRunning);
  TTestPhase = (tpSetUp, tpRunTest, spTearDown, tpNothing);


  { TTestInsightResult }

  TTestInsightResult = Class
  Public
    TestResult : TTestResultType;
    TestName: string;
    TestDuration: Cardinal;
    TestUnitName: string;
    TestClassName: string;
    TestMethodName: string;
    // Status
    TestStatus: string;
    TestExceptionMessage: string;
    TestExceptionClass: string;
    TestIsIgnored : Boolean;
    TestPhase : TTestPhase;
    // Failure info
    FailureUnitName: string;
    FailureMethodName: string;
    FailureLineNumber: Integer;
    FailureSourceUnitName : String; // Exception
    FailureLocationInfo : String;
    Constructor Create; virtual;
    procedure FromJSON(const aJSON : TJSONStringType); overload;
    procedure FromJSON(const aJSON : TJSONObject); overload;
    function ToJSON: string;
    Procedure ToJSON(aJSON : TJSONObject);
  end;
  TTestInsightResultClass = Class of TTestInsightResult;
  TTestInsightResultArray = Array of TTestInsightResult;

  { TTestInsightOptions }

  TTestInsightOptions = Class (TPersistent)
  Public
    ExecuteTests: Boolean;
    ShowProgress: Boolean;
    TestSuite : String;
    Constructor Create; virtual;
    Procedure Assign(Source : TPersistent); override;
    procedure FromJSON(const aJSON : TJSONStringType); overload;
    procedure FromJSON(const aJSON : TJSONObject); overload;
    function ToJSON: string;
    Procedure ToJSON(aJSON : TJSONObject);
  end;
  TTestInsightOptionsClass = Class of TTestInsightOptions;

Const
  // Used in URLS
  DefaultUrl = 'http://localhost:8081/tests';

  pathTests = '/tests';
  pathStarted = 'started';
  pathFinished = 'finished';
  pathResults = 'results';
  pathOptions = 'options';
  qryTotalCount = 'totalcount';

  // Used in TTestInsightResult JSON encoding.
  keyTestName = 'testname';
  KeyTestUnitName = 'testunitname';
  keyTestClassName = 'testclassname';
  keyTestMethodName = 'testmethodname';
  keyTestDuration = 'testduration';

  keyResultType = 'testresulttype';
  keyExceptionMessage = 'exceptionmessage';
  KeyExceptionClass = 'exceptionclass';

  keyfailureMethodName = 'failuremethodname';
  keyFailureUnitName = 'failureunitname';
  keyFailureLineNumber = 'failurelinenumber';
  KeyFailureSourceUnitName = 'failuresourceunitname';
  KeyFailureLocationInfo = 'location';

  keyStatus = 'status';
  keyIsIgnored = 'ignored';
  KeyTestPhase = 'phase';

  // Config file settings
  SConfig = 'Config';
  KeyBaseURL = 'BaseUrl';

  // Used in TTestInsightOptions JSON encoding and Ini file
  keyExecuteTests = 'ExecuteTests';
  keyShowProgress = 'ShowProgress';
  KeySuite = 'Suite';

  ResultTypeNames: array[TTestResultType] of string
    = ('?','Passed', 'Failed', 'Error', 'Warning', 'Skipped', 'Running');
  TestPhaseNames: array[TTestPhase] of string
    = ('Setup', 'Run', 'TearDown','');

Function StringToResultType(aValue : string) : TTestResultType;
Function ResultTypeToString(aValue : TTestResultType) : string;

Function StringToTestPhase(aValue : string) : TTestPhase;
Function TestPhaseToString(aValue : TTestPhase) : string;


// Convert CR/LF separated list name to expected JSON format
Function TestStringsToJSON(aContent : String): TJSONObject;


implementation

Function ResultTypeToString(aValue : TTestResultType) : string;
begin
  Result:=ResultTypeNames[aValue];
end;

function StringToTestPhase(aValue: string): TTestPhase;
Var
  T : TTestPhase;

begin
  Result:=tpNothing;
  For T in TTestPhase do
    if SameText(TestPhaseNames[T],aValue) then
      Exit(T);
end;

function TestPhaseToString(aValue: TTestPhase): string;
begin
  Result:=TestPhaseNames[aValue];
end;

Function StringToResultType(aValue : string) : TTestResultType;

Var
  T : TTestResultType;

begin
  Result:=rtFailed;
  For T in TTestResultType do
    if SameText(ResultTypeNames[T],aValue) then
      Exit(T);
end;

Procedure AddTests(aParent : TJSONObject; aList : TStrings; aPath : String; var aIdx : Integer);

Var
  P : integer;
  Up : Boolean;
  Curr,Sub : String;
  Obj : TJSONObject;

begin
  Repeat
    Curr:=aList[aIdx];
    Up:=(aPath<>'') and Not SameText(Copy(Curr,1,Length(aPath)),aPath);
    if not Up then
      begin
      Delete(Curr,1,Length(aPath));
      if Curr[1]='.' then delete(Curr,1,1);
      P:=Pos('.',Curr);
      if P<>0 then
        begin
        Sub:=Copy(Curr,1,P-1);
        Obj:=TJSONObject.Create;
        aParent.Add(Sub,Obj);
        if aPath<>'' then
          AddTests(Obj,aList,APath+'.'+Sub,aIdx)
        else
          AddTests(Obj,aList,Sub,aIdx)
        end
      else
        begin
        aParent.Add(Curr,TJSONNull.Create);
        Inc(aIdx);
        end;
      end
  Until (aIdx>=aList.Count) or Up;
end;


Function TestStringsToJSON(aContent : String): TJSONObject;

Var
  L : TStringList;
  aPath : String;
  aIdx : Integer;

begin
  Result:=nil;
  L:=TStringList.Create;
  try
    L.Text:=aContent;
    L.Sort;
    Result:=TJSONObject.Create;
    aPath:='';
    aIdx:=0;
    AddTests(Result,L,aPath,aIdx);
  finally
    L.Free;
  end;
end;

{ TTestInsightOptions }

constructor TTestInsightOptions.Create;
begin
  ExecuteTests:=True;
  ShowProgress:=True;
end;

procedure TTestInsightOptions.Assign(Source: TPersistent);

var
  Src : TTestInsightOptions absolute source;

begin
  if Source is TTestInsightOptions then
    begin
    ExecuteTests:=Src.ExecuteTests;
    ShowProgress:=Src.ShowProgress;
    TestSuite:=Src.TestSuite;
    end
  else
    inherited Assign(Source);
end;

procedure TTestInsightOptions.FromJSON(const aJSON: TJSONStringType);
Var
  D : TJSONData;

begin
  D:=GetJSON(aJSON);
  try
    if D is TJSONObject then
      FromJSON(D as TJSONObject);
  finally
    D.Free;
  end;
end;

procedure TTestInsightOptions.FromJSON(const aJSON: TJSONObject);

begin
  with aJSON do
    begin
    ExecuteTests:=Get(KeyExecuteTests,ExecuteTests);
    ShowProgress:=Get(KeyShowProgress,ShowProgress);
    TestSuite:=Get(KeySuite,TestSuite);
    end;
end;

function TTestInsightOptions.ToJSON: string;

Var
  Obj : TJSONObject;

begin
  Obj:=TJSONObject.Create;
  try
    ToJSON(Obj);
    Result := Obj.AsJSON;
  finally
    Obj.Free;
  end;
end;

procedure TTestInsightOptions.ToJSON(aJSON: TJSONObject);

begin
  with aJSON do
    begin
    Add(KeyExecuteTests,ExecuteTests);
    Add(KeyShowProgress,ShowProgress);
    Add(KeySuite,TestSuite);
    end;
end;


{ TTestInsightResult }

constructor TTestInsightResult.Create;
begin
  TestResult:=rtUnknown;
end;

procedure TTestInsightResult.FromJSON(const aJSON: TJSONStringType);

Var
  D : TJSONData;

begin
  D:=GetJSON(aJSON);
  try
    if D is TJSONObject then
      FromJSON(D as TJSONObject);
  finally
    D.Free;
  end;
end;

procedure TTestInsightResult.FromJSON(const aJSON: TJSONObject);

begin
  With aJSON Do
    begin
    TestName:=Get(KeyTestName, TestName);
    TestUnitName:=Get(KeyTestUnitName, TestUnitName);
    TestClassName:=Get(KeyTestClassName, TestClassName);
    TestMethodName:=Get(KeyTestMethodName, TestMethodName);

    TestResult:=StringToResultType(Get(KeyResultType, ResultTypeNames[TestResult]));
    TestDuration:=Get(KeyTestDuration, TestDuration);
    TestStatus:=Get(Keystatus, TestStatus);
    TestIsIgnored:=Get(keyIsIgnored,TestIsIgnored);
    TestPhase:=StringToTestPhase(Get(KeyTestPhase,TestPhaseToString(TestPhase)));

    TestExceptionMessage:=Get(KeyExceptionMessage, TestExceptionMessage);
    TestExceptionClass:=Get(KeyExceptionClass, TestExceptionClass);

    FailureUnitName:=Get(KeyFailureUnitName, FailureUnitName);
    FailureMethodName:=Get(KeyFailureMethodName, FailureMethodName);
    FailureLineNumber:=Get(KeyFailureLineNumber, FailureLineNumber);
    FailureSourceUnitName:=Get(KeyFailureSourceUnitName,FailureSourceUnitName);
    FailureLocationInfo:=Get(KeyFailureLocationInfo,FailureLocationInfo);
    end;
end;

function TTestInsightResult.ToJSON: string;

Var
  Obj : TJSONObject;

begin
  Obj:=TJSONObject.Create;
  try
    ToJSON(Obj);
    Result := Obj.AsJSON;
  finally
    Free;
  end;
end;

procedure TTestInsightResult.ToJSON(aJSON: TJSONObject);
begin
  With aJSON do
    begin
    Add(KeyTestname, TestName);
    Add(KeyTestUnitName, TestUnitName);
    Add(KeyTestClassName, TestClassName);
    Add(KeyTestMethodName,TestMethodName);

    Add(KeyResultType, ResultTypeNames[TestResult]);
    Add(KeyTestDuration, TestDuration);
    Add(KeyStatus, TestStatus);
    Add(keyIsIgnored,TestIsIgnored);
    Add(KeyTestPhase,TestPhaseToString(TestPhase));

    Add(KeyExceptionMessage, TestExceptionMessage);
    Add(KeyExceptionClass, TestExceptionClass);

    Add(KeyFailureUnitName, FailureUnitName);
    Add(KeyFailureMethodName, FailureMethodName);
    Add(KeyFailureLinenumber, FailureLineNumber);
    Add(KeyFailureSourceUnitName,FailureSourceUnitName);
    Add(KeyFailureLocationInfo,FailureLocationInfo);
    end;
end;


end.

