/************************************************
Copyright (c)  2013-2014 by Progress Software Corporation. All rights reserved.
*************************************************/
/*------------------------------------------------------------------------
   File        : ProcedureAnnotationInfo
   Purpose     : Stores the information related to the BeforeProcedure,Before, Test, After, AfterProcedure annotations in
                 the test procedure. Contains methods to run specific annotated procedures.
   Syntax      :
   Description :
   Author(s)   : hgarapat
   Created     : Tue Jul 24 11:36:16 IST 2012
   Notes       :
 ----------------------------------------------------------------------*/


USING OpenEdge.Core.AssertionFailedError.
USING OpenEdge.ABLUnit.Reflection.AnnotationInfo.
USING OpenEdge.ABLUnit.Reflection.TestInfo.
USING OpenEdge.ABLUnit.Results.TestTestResult.
USING OpenEdge.ABLUnit.Results.TestTypeResult.
USING OpenEdge.Core.Collections.Array.
USING OpenEdge.ABLUnit.Reflection.ClassAnnotationInfo.

CLASS OpenEdge.ABLUnit.Reflection.ProcedureAnnotationInfo:

    DEFINE PRIVATE VARIABLE beforeProcMethod   AS  TestInfo   NO-UNDO.
    DEFINE PRIVATE VARIABLE beforeMethod       AS TestInfo    NO-UNDO.
    DEFINE PRIVATE VARIABLE testList           AS Array NO-UNDO.
    DEFINE PUBLIC PROPERTY testCount           AS INTEGER NO-UNDO GET. SET.
    DEFINE PRIVATE VARIABLE afterMethod        AS TestInfo    NO-UNDO.
    DEFINE PRIVATE VARIABLE afterProcMethod    AS TestInfo    NO-UNDO.
    DEFINE PRIVATE VARIABLE testInProcResult   AS TestTestResult NO-UNDO.

    /*------------------------------------------------------------------------------
     Purpose:
     Notes:
    ------------------------------------------------------------------------------*/

    CONSTRUCTOR PUBLIC ProcedureAnnotationInfo (  ):
        SUPER ().
        testList = NEW Array().
        testList:AutoExpand = TRUE.
    END CONSTRUCTOR.

    METHOD PUBLIC VOID setBeforeProcedure(INPUT bProc AS TestInfo):
        IF beforeProcMethod = ? THEN
            beforeProcMethod = bProc.
        ELSE
            throwError().
    END METHOD.

    METHOD PUBLIC VOID setAfterProcedure(INPUT aProc AS TestInfo):
        IF afterProcMethod = ? THEN
            afterProcMethod = aProc.
        ELSE
            throwError().
    END METHOD.

    METHOD PUBLIC VOID setBefore(INPUT beforeProc AS TestInfo):
        IF beforeMethod = ? THEN
            beforeMethod = beforeProc.
        ELSE
            throwError().
    END METHOD.

    METHOD PUBLIC VOID setAfter(INPUT afterProc AS TestInfo):
        IF afterMethod = ? THEN
            afterMethod = afterProc.
        ELSE
            throwError().
    END METHOD.

    METHOD PUBLIC VOID addTestProc(INPUT tProc AS TestInfo):
        testCount = testCount + 1.
        testList:SetValue(tProc, testCount).
    END METHOD.

    METHOD PUBLIC CHARACTER getBeforeProcedure():
        RETURN beforeProcMethod:getTestName().
    END METHOD.

    METHOD PUBLIC CHARACTER getAfterProcedure():
        RETURN afterProcMethod:getTestName().
    END METHOD.

    METHOD PUBLIC Array getTestProcs():
        RETURN testList.
    END METHOD.

    /*------------------------------------------------------------------------------
    Runs @BeforeProcedure annotated procedure
    ------------------------------------------------------------------------------*/
    METHOD PUBLIC TestTypeResult RunBeforeProcedure(INPUT procHandle AS HANDLE, INPUT testProcResult AS TestTypeResult):
        IF NOT beforeProcMethod = ? AND NOT testList:Size = 0 THEN
        DO ON ERROR UNDO, THROW:
            DEFINE VARIABLE beforeProc AS CHARACTER NO-UNDO.
            beforeProc = beforeProcMethod:getTestName().
            RUN value (beforeProc) IN procHandle NO-ERROR.
            IF (ERROR-STATUS:ERROR) THEN
                RETURN ERROR "Exception in @BeforeProcedure".
        END.
        RETURN testProcResult.
    END METHOD.

    /*------------------------------------------------------------------------------
    Runs @Before annotated procedure
    ------------------------------------------------------------------------------*/
    METHOD PUBLIC TestTestResult RunBefore(INPUT procHandle AS HANDLE, INPUT testProcResult AS TestTestResult):
        IF NOT beforeMethod = ? THEN
        DO ON ERROR UNDO, THROW:
            DEFINE VARIABLE before AS CHARACTER NO-UNDO.
            before = beforeMethod:getTestName().
            RUN value (before) IN procHandle NO-ERROR.
            IF (ERROR-STATUS:ERROR) THEN
                RETURN ERROR "Exception in @Before procedure".
        END.
        RETURN testProcResult.
    END METHOD.

    /*------------------------------------------------------------------------------
    Runs a particular @Test annotated procedure
    ------------------------------------------------------------------------------*/
    METHOD PUBLIC TestTestResult RunTestProc(INPUT procHandle AS HANDLE, INPUT testProc AS TestInfo, INPUT testInProcResult AS TestTestResult):

        RUN value (testProc:getTestName()) IN procHandle.
         CATCH e AS Progress.Lang.Error :
             RETURN ERROR e.
        END CATCH.

    END METHOD.

    /*------------------------------------------------------------------------------
    Runs all @Test annotated procedure
    ------------------------------------------------------------------------------*/
    METHOD PUBLIC TestTestResult RunSelectedTestProc(INPUT procHandle AS HANDLE, INPUT testProc AS TestInfo):
        DEFINE VARIABLE expectedException AS CHARACTER NO-UNDO.
        DEFINE VARIABLE annotations AS Array NO-UNDO.
        DEFINE VARIABLE annotation AS AnnotationInfo NO-UNDO.
        annotations = testProc:annotations.
        /** considering only the 1st annotation for now**/
        annotation = CAST(annotations:GetValue(1), AnnotationInfo ).

        expectedException = annotation:getAttributeValue().
        testInProcResult = NEW TestTestResult().
        RunBefore(procHandle, testInProcResult).
        RunTestProc(procHandle, testProc, testInProcResult).
        /*Ensure that the execution proceeds normally and there are no expected exceptions*/
        IF expectedException = "" THEN DO:
            testInProcResult:TestResult = TestTestResult:StatusPassed.
            testInProcResult:TestName = testProc:getTestName().
        END.
        ELSE
        DO:
            testInProcResult:TestResult = TestTestResult:StatusFailed.
            testInProcResult:TestName = testProc:getTestName().
            testInProcResult:ErrorMessage = "Expecting:" + expectedException + " but this expection is not raised.".
            DEFINE VARIABLE exception AS Progress.Lang.Error NO-UNDO.
            exception = NEW Progress.Lang.AppError("No exception is raised but expecting " + expectedException, 1).
            testInProcResult:Error = exception.
        END.
        CATCH e AS Progress.Lang.Error :
            IF expectedException = "" THEN
            DO:

                /* Mike Fechner, Consultingwerk Ltd. 27.05.2016
                   Handle Consultingwerk AssertException (and derived types
                   like ABLUnit AssertFailedError */
                {Consultingwerk/Studio/AblUnit/11_6_0/handle-consultingwerk-exception2.i testInProcResult testProc}

                IF (e:GetClass():TypeName = "OpenEdge.Core.AssertionFailedError") THEN
                DO:
                    testInProcResult:TestResult  = TestTestResult:StatusFailed.
                    testInProcResult:TestName = testProc:getTestName().
                    testInProcResult:Error = e.
                    testInProcResult:ErrorMessage = e:GetMessage(1).
                END.
                ELSE
                DO:
                    testInProcResult:TestResult  = TestTestResult:StatusError.
                    testInProcResult:TestName = testProc:getTestName().
                    testInProcResult:Error = e.
                END.
            END.
            ELSE IF (ClassAnnotationInfo:IsTypeOf(e, ClassAnnotationInfo:GetExpectedErrorType(expectedException))) THEN
            DO:
                testInProcResult:TestResult = TestTestResult:StatusPassed.
                testInProcResult:TestName = testProc:getTestName().
            END.
            ELSE
            DO:
                testInProcResult:TestResult = OpenEdge.ABLUnit.Results.TestTestResult:StatusFailed.
                testInProcResult:TestName = testProc:getTestName().
                testInProcResult:ErrorMessage = "Expecting:" + expectedException + " but found:" + e:GetClass():TypeName.
                testInProcResult:Error = e.
            END.
        END CATCH.
        FINALLY:
            RunAfter(procHandle).
            CATCH e AS Progress.Lang.Error :
                testInProcResult:TestResult = TestTestResult:StatusError.
                updateTestResult(testInProcResult, e, testProc).
            END CATCH.
            FINALLY:
                RETURN testInProcResult.
            END FINALLY.
        END FINALLY.
    END METHOD.

    METHOD PUBLIC VOID updateTestResult(INPUT testResult AS TestTestResult,INPUT err AS Progress.Lang.Error,  INPUT testProc AS TestInfo):
            testResult:TestName = testProc:getTestName().
            testResult:IsIncomplete = TRUE.
            testResult:Error = err.
        END METHOD.

    /*------------------------------------------------------------------------------
    Runs @After annotated procedure
    ------------------------------------------------------------------------------*/
    METHOD PUBLIC VOID RunAfter(INPUT procHandle AS HANDLE):
        IF NOT afterMethod = ? THEN
        DO ON ERROR UNDO, THROW:
            DEFINE VARIABLE after AS CHARACTER NO-UNDO.
            after = afterMethod:getTestName().
            RUN value (after) IN procHandle NO-ERROR.
            IF (ERROR-STATUS:ERROR) THEN
                RETURN ERROR "Exception in @After procedure".
        END.
    END METHOD.

    /*------------------------------------------------------------------------------
    Runs @AfterProcedure annotated procedure
    ------------------------------------------------------------------------------*/
    METHOD PUBLIC TestTypeResult RunAfterProcedure(INPUT procHandle AS HANDLE):
        IF NOT afterProcMethod = ? AND NOT testList:size = 0 THEN
        DO:
            DEFINE VARIABLE afterProc AS CHARACTER NO-UNDO.
            afterProc = afterProcMethod:getTestName().
            RUN value (afterProc) IN procHandle NO-ERROR.
            IF (ERROR-STATUS:ERROR) THEN
                RETURN ERROR "Exception in @AfterProcedure".
        END.
    END METHOD.

    METHOD PUBLIC VOID TestMethod(  ):

        RETURN ERROR NEW Progress.Lang.AppError().

    END METHOD.

    /*------------------------------------------------------------------------------
        Throws error for multiple occurence of lifecycle methods
    ------------------------------------------------------------------------------*/
    METHOD PRIVATE VOID throwError( ):
        RETURN ERROR NEW Progress.Lang.AppError("Life cycle annotations can not occur more than once.", 0).
    END METHOD.
END CLASS.