Skip to content

Commands reference

Script-A-Rest primarily includes commands to perform server calls, handle variables and orchestrate scripts. It also includes few other useful utilities and commands for performing assertions.

For information on how to start Test-A-Rest, have a look at the README.md file. It also shows the possible command line arguments.

Commands

Commands are structured as one line with command and arguments with an optional JSON block.

Commands must begin at first position on a line. Command keywords are case insensitive. So Get SomeSvc.svc/SomeEntitySet is the same as Get SomeSvc.svc/SomeEntitySet.

Generally URLs, JSON payloads and variable references are case sensitive. So Get SomeSvc.svc/SomeEntitySet is NOT the same as Get someSvc.svc/someentityset.

Line comments can begin either with // or --. Comments be on first position on line. Comments after commands does not work.

Data types

Variables in Script-A-Rest are either basic types like string, int, double, boolean, DateTime or compound JSON types, i.e. containing an JSON object of any size and/or complexity.

JSON only supports string, numeric, boolean and null basic values. Complex JSON values are objects or arrays. Due to lack of JavaScript number precision, FndODataProvider and IFS Cloud Web Framework serializes numbers as strings (using IEEE 754 header). Script-A-Rest sends requests with IEEE 754 header and therefore any JSON values sent must be quoted as strings.

Example:

{
    "pi": "3.14"
}

Available Commands

Server calls

Script variable handling

Calling other scripts

Other commands

Other references

Post

Call URL with HTTP POST

Post can be done conditionally by using then When keyword.

(Also see the alternatives Action and Create)

Syntax

Post {ExpectFail} [URL path to service] {Using variableWithEtag} {Into [resultVariable] } {When [condition]}
JSON Object Body

Example

Post DockCodesHandling.svc/PurchaseDockCodes Into createResult1
{
    "Contract": "1",
    "DockCode": "DockA",
    "Description": "Main Gate"
}

Into section and variable is optional. If specified data returned from server will be placed here.

Post DockCodesHandling.svc/PurchaseDockCodes
{
    "Contract": "1",
    "DockCode": "DockA",
    "Description": "Main Gate"
}

Using section is optional and needed when calling actions requiring ETag, eg entity-bound actions. When Using is used, Into is mandatory

Post DockCodesHandling.svc/PurchaseDockCodes('DockA')/Ifsapp.DockCodesHandling.Release Using getResult1 Into createResult1
{
    "Extraparams": "1",
}

Sometimes a call shouldn't be executed unless a certain condition is met.

Post DockCodesHandling.svc/PurchaseDockCodes('DockA')/Ifsapp.DockCodesHandling.Release Using getResult1 Into createResult1 When 1==1
{
    "Extraparams": "1",
}

See ExpectFail on server calls for details about ExpectErrors keyword

Get

Call URL with HTTP GET verb.

Get can be done conditionally by using then When keyword.

Syntax

Get {ExpectFail} [URL path to service] Into [resultVariable] When [condition]

Examples (Assuming result of Get command will be used in the following command(s), here CopyJson.)

Example 1

Get DockCodesHandling.svc/PurchaseDockCodes(Contract='1',DockCode='A') Into getResult
CopyJson Description Using getResult Into response 

Example 2

Get DockCodesHandling.svc/PurchaseDockCodes(Contract='1',DockCode='A') Into getResult When 1==1
CopyJson Description Using getResult Into response When 1==1 

Example 3

ApplyJson Into getResult
{
}
Get IsoUnitsOfMeasureHandling.svc/IsoUnitSet(UnitCode='{$input.UnitCode}') Into getResult When 1==1
CopyJson UsedInAppl Using input into response When 1==1 && getResult.UserDefined==false

Into section and variable is mandatory and data returned from server will be placed here.

When section is optional. If specified and condition is met, data returned from the server will be placed in Into variable.

See ExpectFail on server calls for details about ExpectErrors keyword

See Add Header to Http Request Example for details of adding headers to the request

Query

Call URL with HTTP GET verb with options Select, OrderBy and Filter

Syntax

Query {ExpectFail} [URL path to service] {Select "Attr1,Attr2"} {OrderBy "Attr1 desc,Attr2"} {Filter With Json} Into [resultVariable] When [condition]

Select

The Select query option allows the clients to requests a limited set of properties for each entity.

OrderBy

The OrderBy query option allows clients to request resources in either ascending order or descending order using desc.

Filter

The Filter query option allows clients to filter a collection of resources that are addressed by a request URL. The expression specified for the filter is evaluated for each resource in the collection, and only items where the expression evaluates to true are included in the response.

Filter With Json Expressions are evaluated with AND between different attributes and OR within an attribute. Default operator is equal.

A Json Filter in it's simplest form: Returns items for expression Contract=A

{
   "Contract": "A"
}

A Json Filter with Multiple attributes: Returns items for expression ((OrderNo=1 OR OrderNo=2) AND (Contract=A OR Contract=B OR Contract=C))

{
   "OrderNo": "1;2",
   "Contract": "A;B;C"
}

You can use variables like "OderNo" : "{$input.OrderNo}" inside the Json for the Filter query option. However note that only $ and # works for automatic quotation of string values. Using % does not work due to defect TAR-252.

Filter Operators

Default operator is equal if no operator is specified.

Supported operators:

  • >= Greater Than Or Equal
  • > Greater Than
  • <= Less Than Or Equal
  • < Less Than
  • != Not Equal
  • = Equal

A Json Filter with operator: Returns items for expression OrderNo>=5

{
   "OrderNo": ">=5"
}

Filter Data Types

String

{
   "Attribute1": "5",
   "Attribute2": "5;7;10"
}

Number

{
   "Quantity1": 5,
   "Quantity2": {"Number": "5;7;10"}
}

Enumeration

{
    "Enumeration1": {"Enumeration": {"Enum.SomeEnum": "Value1;Value2"}}
}

Date : Can either be a single day or a range.

{
    "Date1": {"Date": "2020-12-01"},
    "Date2": {"Date": "2020-12-01;2020-12-23"}
}
DateTime : Can either be a single day or a range.
json linenums="1"
{
    "DateTime1": {"DateTime": "2020-12-01"},
    "DateTime2": {"DateTime": "2020-12-01;2020-12-23"}
}

Filter Examples

Example 1 :

Query ShopOrdersHandling.svc/ShopOrds Select "OrderNo,Contract" OrderBy "Contract,OrderNo desc" Filter With Json Into queryResult
{
   "Contract": "MF-S1",
   "OrderNo": ">=204129",
   "EffPhaseInDate": {"Date": "2021-01-07;2021-01-08"}
}

Patch

Change an entity URL (HTTP PATCH). Requires an object retrieved either by a Get or Create command.

Patch can be done conditionally by using then When keyword.

(Also see the Modify which is an alias for Patch)

Syntax

Patch {ExpectFail} [URL path to service] Using [variable] Into [resultVariable] When [condition]
JSON Object Body

Example : (assuming result retrieved earlier, see Get or Create)

Patch DockCodesHandling.svc/PurchaseDockCodes(Contract='1',DockCode='DockA') Using getResult1 Into patchResult1 When 1==1
{
    "Description": "Changed description"
}

Only the attributes changed needs to be provided in JSON Object body. Not sent attributes will be unchanged.

Flow for Modify * Retrieve @odata.etag from provided variable (getResult1) * Perform PATCH call to service * Retrieve result from server and place it in patchResult1.

This flow makes it possible to perform subsequent modify calls

See ExpectFail on server calls for details about ExpectErrors keyword

See Add Header to Http Request Example for details of adding headers to the request

Upsert Functionality using Patch

A record can be inserted (if it does not already exist), or modified using the Patch command.

An empty Json object can be used instead of the results retrieved from Get or Create calls as shown in the example below

ApplyJson Into result
{
}
Patch DockCodesHandling.svc/PurchaseDockCodes(Contract='1',DockCode='DockA') Using result Into patchResult1
{
    "Contract": "1",
    "Description": "Description",
    "DockCode": "DockA"
}

PatchBlob

Change an entity URL (HTTP PATCH) with the purpose to modify an Edm.Stream attribute (eg picture or document). Requires an object retrieved either by a Get or Create command.

PatchBlob can be done conditionally by using then When keyword

(Also see the ModifyBlob which is an alias for Patch)

Syntax

PatchBlob {ExpectFail} [URL path to service] Using [variable] When [condition]
{
    "fileReference": "[aFileReference]"
}

Example (assuming result retrieved earlier, see Get or Create)

PatchBlob PersonHandling.svc/PersonInfoSet(PersonId='TOST')/PersonImage Using personInfo1 When 1==1
{
    "fileReference": ".\\Tony-Stark.jpg"
}

Body must contain the only attribute fileReference with a value containing the file name (and optional file path). If no absolute path is specified, path is determined relative to the script location (folder).

Flow for Modify * Retrieve @odata.etag from provided variable (getResult1) * Perform PATCH call to service PATCH call will upload data as application/octet-stream and send an X-IFS-Content-Disposition header with file name * Since server sends an empty response, the provided variable (getResult1) will be empty and cannot be used for subsequent calls.

See ExpectFail on server calls for details about ExpectErrors keyword

Delete

Delete an entity URL (HTTP DELE). Requires an object retrieved either by a Get or Create command

Delete can be done conditionally by using the When keyword.

Syntax

Delete {ExpectFail} [URL path to service] Using [variable] When [condition]

Example 1 (assuming result retrieved earlier, see Get or Create)

Delete DockCodesHandling.svc/PurchaseDockCodes(Contract='1',DockCode='DockA') Using getResult1
Example 2 (Using Into with Delete to get the response)
Delete DockCodesHandling.svc/PurchaseDockCodes(Contract='1',DockCode='DockA') Using getResult1 Into response
Example 3 (Using When as sometimes a call shouldn't be executed unless a certain condition is met)
Delete DockCodesHandling.svc/PurchaseDockCodes(Contract='1',DockCode='DockA') Using getResult1 When 1==1

No body payload should be sent. Only key and etag (objversion) is needed for delete.

Flow for Modify * Retrieve @odata.etag from provided variable (getResult1) * Perform Delete call to service * No result from server. Variable is unchanged

See ExpectFail on server calls for details about ExpectErrors keyword

Batch

Batch a number of HTTP request methods into one call. Sometimes it's necessary to perform a number of requests within the same session, then Batch can be used.

Syntax

Batch {ExpectFail} [URL path to service] {Using variableWithEtagsArray} {Into [resultVariableArray] } {When [condition]}
JSON Array Body

Example

Batch SalesRuleHandling.svc Into myBatch
[
    {"POST ValidateCondition":
    {"Init":true}},
    {"POST ConfigSalesRuleSet(SalesRuleId={$myId})/ConditionsArray":
    {
        "SalesRuleId":{%myId},
        "Value":"CNV1",
        "CompareValue":"5",
        "ConfigRelationalOperator":"EqualTo",
        "CompareConditionType":"Value",
        "ConditionType":"CharValue",
        "ConfigValueType":"VARIABLEVALUE"
    }},
    {"POST ConfigSalesRuleSet(SalesRuleId={$myId})/ConditionsArray":
    {
        "SalesRuleId":{%myId},
        "Value":"CNV1",
        "CompareValue":"6",
        "ConfigRelationalOperator":"EqualTo",
        "ConfigLogicOperator":"And",
        "CompareConditionType":"Value",
        "ConditionType":"CharValue",
        "ConfigValueType":"VARIABLEVALUE"
    }},
    {"POST ValidateCondition":
    {"Init":false}}
]

Into section is optional. If specified, data returned from the server will be placed in the succeeding variable. For Batch calls it will stored as an array of responses. One array object for each call in the batch.

Using section is optional and needed when calling actions requiring ETag, eg entity-bound actions. When calling Batch with Using it shall be on array format and the ETag corresponding to the n'th call should be located in the n'th object in the array.

Batch SalesRuleHandling.svc Using myBatch.value Into myBatch2
[
    {"POST ValidateCondition":
    {"Init":true}},
    {"PATCH ConfigSalesRuleSet(SalesRuleId={$myId})/ConditionsArray(SalesRuleId={$myId},SalesRuleConditionId={$myBatch.value.Items(1).SalesRuleConditionId})":
    {"CompareValue":"7"}},
    {"PATCH ConfigSalesRuleSet(SalesRuleId={$myId})/ConditionsArray(SalesRuleId={$myId},SalesRuleConditionId={$myBatch.value.Items(2).SalesRuleConditionId})":
    {"CompareValue":"8"}},
    {"POST ValidateCondition":
    {"Init":false}}
]

Batch can be done conditionally by using the When keyword. Sometimes a call shouldn't be executed unless a certain condition is met.

Batch SalesRuleHandling.svc Using myBatch.value Into myBatch2 When 1==1
[
    {"POST ValidateCondition":
    {"Init":true}},
    {"PATCH ConfigSalesRuleSet(SalesRuleId={$myId})/ConditionsArray(SalesRuleId={$myId},SalesRuleConditionId={$myBatch.value.Items(1).SalesRuleConditionId})":
    {"CompareValue":"7"}},
    {"PATCH ConfigSalesRuleSet(SalesRuleId={$myId})/ConditionsArray(SalesRuleId={$myId},SalesRuleConditionId={$myBatch.value.Items(2).SalesRuleConditionId})":
    {"CompareValue":"8"}},
    {"POST ValidateCondition":
    {"Init":false}}
]

See ExpectFail on server calls for details about ExpectErrors keyword

JSON Array Body

The JSON Array Body consists of a number of JSON Objects each representing a request. Each JSON Object has key: "[HTTP Request method] [relative URL path to service]" value: [JSON Object body].

[
    {"POST ValidateCondition":
    {"Init":true}}
]

Eval

Evaluate expression (in C#) syntax and place result in variable. Evaluation can be done conditionally by using then When keyword

Eval [expression] Into [variable] {When [condition]}

Expression evaluator is using the "DynamicExpresso" library. See more info here.

Examples

Store constant string "1" in s1.

Eval "1" Into s1

Add 10 + 15 and store result (25) in i2

Eval 10 + 15 Into i2

Add 100 to result i2 and store (125) in i3. Makes use of variables, see Variables section.

Eval {$i2} + 100 Into i3

Treat i2 as a string and append "100". Store result ("125100") in s2.

Eval "{$i2}" + "100" Into s2

Eval currently supports the following functions and procedures * Random()

Eval Random() Into i4

Conditional evaluation of expressions

Sometimes an expression shouldn't be evaluated unless a certain condition is met.

This can be done with Eval [expr] Into [var] When [condition] syntax. Only if the condition is met, the [expr] will be evaluated into [var].

This can be useful for default handling combined with InputExists() and VariableExists() functions

Eval input.someInput Into someVar When InputExists("someInput") == true
Eval "DefaultString" Into someVar When InputExists("someInput") == false

Two functions are available - InputExists(<string> variableName) - Return true variableName is passed as input to a script. False otherwise - VariableExists(<string> variableName) - Return true variableName is created as local variable within a script. False otherwise

### Response Status Code

Http Status code is returned back in the body of the response and this can be used to conditionally evaluate expressions or do conditional calling

The key value is ResponseStatusCode.

[Do not use this keyword as a JSON key value in the request body. Since the tool will remove it from the requests body before making API calls]

The value of this key can be used in conditions specially when needed to check a deleted record which would have the status code 204

Eval reponse.value Into tempVar When response.ResponseStatusCode != 204

ApplyJson Using someVar Into someVar When response.ResponseStatusCode != 204
{
  "someInput" : {%response.value}
}

Works for batch requests as well.

ApplyJson

Applies provided JSON upon existing (Using) JSON variable and store result in new variable.

Any value specified in JSON Body will overwrite or create corresponding JSON value in the used (Using) JSON.

If using isn't specified, ApplyJson will create JSON values.

Can be done conditionally by using then When keyword.

Syntax

ApplyJson {Using <fromVariable>} Into <destVar>
{ JSON Body }

Example

Create an JSON object with s1 = "Hello" and s2 = "World".

ApplyJson Into test1
{
    "s1": "Hello",
    "s2": "World",
}

Change s2 value (from test1 above) into "Universe". Add a new numeric value n1=3.14

ApplyJson Using test1 Into test2
{
    "s2": "Universe",
    "n1": "3.14"
}
Results in test2

{
    "s1": "Hello",
    "s2": "Universe",
    "n1": "3.14"
}
Add variable if condition is met.

Eval "Hello" Into s1

ApplyJSON Into test3 When VariableExists("s1")
{
    "s1": {%s1}
}
Results in test3
{
    "s1": "Hello"
}

CopyJson

Copy specified JSON values from one variable to another.

Any jsonvalue in the destination (Into) that is copied from source will be overwritten or created.

Can be done conditionally by using then When keyword.

Syntax

CopyJson <jsonvalues> Using <fromSomeVar1> Into <someVar2>

Examples

Example 1

Consider a the following JSON stored in testVar1

{
    "s1": "Hello",
    "s2": "World",
    "n1": 3.14
}

Execute the following command

CopyJson <s1,s2> Using testVar1 Into testVar2

testVar2 will contain the following JSON

{
    "s1": "Hello",
    "s2": "World"
}

Example 2

Consider the following JSON is stored in testVar1.

{
    "s1": null,
    "s2": "Universe",
    "n1": 1.41
}

and the following JSON stored in testVar2

{
    "n1": 3.14
}

Execute the following command

CopyJson <s1,n1> Using testVar1 Into testVar2

testVar2 will contain the following JSON

{
    "s1": null,
    "n1": 1.41
}

Example 3

Add variable if condition is met. Consider the following JSON is stored in testVar3.

{
    "s1": null,
    "n1": 1.41
}

Execute the following commands.

Eval "testVar3.n1" Into n1

CopyJson <n1> Using testVar3 Into testVar4 When VariableExists("n1")
Results in testVar4
{
    "n1": 1.41
}

RemoveJson

Remove specified JSON values from a variable. Specified JSON values that does not exists will be ignored.

Can be done conditionally by using then When keyword.

Syntax

RemoveJson<jsonvalues> Using <fromSomeVar1> Into <someVar2>

Example

Example 1

Consider a the following JSON stored in testVar1

{
    "s1": "Hello",
    "s2": "World",
    "n1": 3.14
}

Execute the following command

RemoveJson <s1,s2> Using testVar1 Into testVar2

testVar2 will contain the following JSON

{
    "n1": 3.14
}

Example 2

Add variable if condition is met. Consider a the following JSON stored in testVar2

{
    "s1": "Hello",
    "s2": "World",
    "n1": 3.14
}

Execute the following command

Eval testVar2.n1 Into n1
RemoveJson <n1> Using testVar2 Into testVar3 When n1 > 1

testVar3 will contain the following JSON

{
    "s1": "Hello",
    "s2": "World"
}

AssertJson

Assert a Json structure towards an expected Json structure and raise error if the expected Json is not equal to or a subset of the expected Json structure.

AssertJson can be done conditionally by using then When keyword.

AssertJson [json] Using [expectedJson] When [condition]

Example

ApplyJson Into json1
{
    "A":"value",
    "B":1,
    "C":{"D":true}
}

ApplyJson Into json2
{
    "A":"otherValue",
    "B":1,
    "C":{"D":true}
}

ApplyJson Into json3
{
    "A":"value",
    "B":1,
    "C":{"D":true, "E":"Hello Hello"}
}

ApplyJson Into json4
{
    "A":"value",
    "B":1,
    "C":{"D":true}
}

Will always pass

AssertJson json1 Using json4
AssertJson json3 Using json1

Will always fail

AssertJson json1 Using json2
AssertJson json1 Using json3

Assert

Evaluate boolean expression (in C# syntax) and raise error if expression evaluates to false.

Assert can be done conditionally by using then When keyword.

Assert [bool-expression] When [condition]

Example

Will always pass

Assert 10+10 == 20

Will always fail

Assert 10+10 == 100

Check result from Eval 10 + 15 Into i2

Assert {$i2} == 25

Check that response from server in Get call is "Main Gate"

Assert "{$getResult1.Description}" == "Main Gate"

Print

Print to console output

Example

ApplyJson Into j1
{
    "s1": "Hello",
    "s2": "World",
}
Eval "abc" Into stringVar

Print j1
Print j1.s2
Print stringVar
Print "String not in a variable"

Call

"Calls" another script file and executes it. When completed execution in current script will continue. Possible to pass parameters for usage in callee script and retrieve results from callee script. call can be done conditionally by using then When keyword.

Call {CatchError} [md-file-refence] {With Json} {Into resultvar} {When [condition]}
Optional Json Object Body if With Json is specified

Example

No parameters. "otherfile.md" located in same folder as current script. Callee script does not share any variables with the caller apart from the parameters passed. There are (currently) no globals. Callee cannot return data to caller.

File references are relative to placement of the script currently executing (that does the Call)

Basic call to other file (without parameters)

Call otherfile.md

Basic call to other file (without parameters) if condition is met

Call otherfile.md When 1==1

Basic call to other file (without parameters). Store output from otherfile.md into a variable someVar

Call otherfile.md Into someVar

Call with parameters - ignoring result from callee

Call otherfile.md With Json
{
    "param1": "value1",
    "param2": 2,
    "param3": "{$p3}"
}

Call with parameters. Store output from otherfile.md into a variable someVar

Call otherfile.md With Json Into someVar
{
    "param1": "value1",
    "param2": 2,
    "param3": "{$p3}"
}

Callee script can access the parameters directly as variables

Example (using parameters above) - store 12 in i2

Eval {$param2} + 10 Into i2

See CatchError on script calls for details about CatchError keyword

Output

Output is the command in a called script (callee) to return data to the caller.

Output always returns a JSON document and can be created in two ways

Outputs a JSON response coming from a server call. In example assume a Get result is stored in variable getResult1)

Output getResult1

Output can also return a crafted JSON

Output
{
    "result1": "value1",
    "result2": 2,
    "result3": "{$p3}"
}

A caller retrieve the return value from the callee by using the Into keyword. Example assume Output from callee above

Call otherfile.md Into someVar
Assert {$someVar.result2} == 2

All caller may ignore return values from a callee.

ExecuteCSV

ExecuteCsv command will use the contents of your comma-separated values (CSV) file as a repeating Json input.

Data.csv :

Id,Make,Model
1,Honda,Civic
2,Toyota,Camry
3,Toyota,Prius

The Using keyword indicates the utility file that takes each CSV row as a Json input.

The Context provides values that you don't want to have in your CSV file, for better configurability.

ApplyJson Into csvContext
{
    "User" : "ALAIN"
}

ExecuteCsv Data.csv Using CreateCars.mkd Context csvContext

CreateCars.mkd :

Create CarsHandling.svc/CarSet Into response
{$input}

Action

Action is an alternative command name used to call actions, instead of using Post. It supports the same features as the three variants of Post used to call actions, and it works with the Using and Into keywords in the same way.

Create

Create is an alternative command name used to create records, instead of using Post. It supports the same features as the variants of Post used to create records, and it works with the Using and Into keywords in the same way.

Modify

Modify is an alias for Patch and therefore works in the same way.

ModifyBlob

ModifyBlob is an alias for PatchBlob and therefore works in the same way.

ModifyClob

Change an entity URL (HTTP PATCH) with the purpose to modify a LongText attribute (CLOB field). Requires an object retrieved either by a Get or Create command.

ModifyClob can be done conditionally by using then When keyword

Syntax

ModifyClob {ExpectFail} [URL path to service] Using [variable] When [condition]
{
    "clobContent": "Text Content"
}

Example (assuming result retrieved earlier, see Get or Create)

ModifyClob ReceiveInventoryPart.svc/FndTempLobs(LobId={#result.LobId})/ClobData Using result When 1==1
{
    "clobContent": "TAR1TAR2TAR3"
}

Body must contain the only attribute clobContent with the text content that need to be sent to the server.

Flow for ModifyClob * Retrive @odata.etag from provided variable (result) * Perform PATCH call to service. PATCH call will upload data as application/octet-stream * Server sends an empty response.

See ExpectFail on server calls for details about ExpectErrors keyword

See Add Header to Http Request Example for details of adding headers to the request

Delay

Pauses the script for the specified number of milliseconds

Example

// Pause for one minute
Delay 60000

Using the Delay command in internal testing scripts is not encouraged.

RequireMinVersion

Aborts the execution if the version of Script-A-Rest that is used to run the script is lower than the required minimum version specified. This way you can assure that your script is not run with an older version of Script-A-Rest than the one you used to develop and test your script.

Syntax

RequireMinVersion <version>

Here the version indicates Script-A-Rest release version.

Example

RequireMinVersion 1.2.0.30

Call otherfile.md

Expect Error on server calls

When doing server calls (Get, Modify, Post, PatchBlob, Patch, Delete, Batch etc), script execution behavior on errors can be defined. Either the call is expected to succeed or expected to fail (ExpectFail).

This language design is intended to increase test script readability/understandability.

What is considered success and failures corresponds to how IFS Cloud Web client frameworks (and IFS Marble) consider success or failure.

Calls without ExpectFail clause

When calling the server without ExpectFail clause, any server error will be seen as fatal and abort the script execution. Success is defined as HTTP response codes in 200-series (200-299). Failures are responses in HTTP 400 and 500 series.

Calls with ExpectFail clause

When doing calls where an error is expected, any server success will be seen as fatal and abort the script execution. This is the opposite behavior compared to calls with out ExpectFail

The response variable will contain the error "record" instead instead of a row record.

A typical error response from FndODataProvider looks like

{ 
    "error": {
        "code": "DATABASE_ERROR", 
        "message":"Unable to serve the request due to Database error.",
        "details": [
            {
                "code":20110,
                "message":"ORA-20110: DockCodesHandling.ETAG_INCORRECT: ETag is incorrect"
            }
        ]
    }
}

Example:

Get ExpectFail DockCodesHandling.svc/PurchaseDockCodes(Contract='missing',DockCode='DockA') Into getErrorResult1

Assert getErrorResult1.error.code == "DATABASE_ERROR"

Catch Error on Script Calls

When doing script calls, it is possible to limit the script execution abort with CatchError. This will allow following test scripts to execute even if a server call fails or an exception is thrown within a test script.

Example:

Call CatchError TestCase1.md
Call CatchError TestCase2.md
In the example above. If a server call fails or an exception is thrown within test script TestCase1, all remaining calls within TestCase1 will aborted. However test script TestCase2 will still be executed.

Substitution handling

In order to build advanced test scripts, request URLs and request body payloads, substitution handling can be used. These can be altered dynamically depending on results from previous calls, external input, or other items.

All substitution variables are enclosed by curly brackets, and the first character inside the curly brackets determines how it will be rendered in code. There are 3 characters to select from. These are {#}, {%}, and {$}, and you select one mainly based on which quotation marks you need to include with the substitution.

  • {#...} The hash symbol will surround the substituted variable with single quotes (except for certain keywords like null or true).

Usage: URLs containing single-quoted values:

Get ProjectScopeAndScheduleHandling.svc/Projects(ProjectId='AT12044834')
//Should be:
Get ProjectScopeAndScheduleHandling.svc/Projects(ProjectId={#projectId})
i1=123; s1="abc"; n1=null; b1=true;
Hello {#i1} World => Hello '123' World
Hello {#s1} World => Hello 'abc' World
Hello {#n1} World => Hello null World
Hello {#b1} World => Hello true World
  • {%...} The percentage symbol will surround the substituted variable with double quotes (except for certain keywords like null or true).

Usage: Suitable for transferring values using Json to scripts, or sending parameters into service calls:

Call ./util/CreateUser.mkd With Json
{
    "UserId":       "BLOBBO",
    "Description":  "Blobbo Guyman",
    "DbPassword":   "donuts"
}
//Replace with:
Call ./util/CreateUser.mkd With Json
{
    "UserId":       {%userName},
    "Description":  {%userDescription},
    "DbPassword":   {%userPassword}
}
i1=123; s1="abc"; n1=null; b1=true;
Hello {%i1} World => Hello "123" World
Hello {%s1} World => Hello "abc" World
Hello {%n1} World => Hello null World
Hello {%b1} World => Hello true World
  • {$...} The dollar symbol only substitutes the value. It adds no quotation marks, and tends to be the least used of the three forms.

Usage: Some Script-A-Rest commands and URLs where values should not be enclosed by quotation marks (ie numbers etc):

ApplyJson Using createParams Into createParams
{$input}

//OR

Get LocationHandling.svc/LocationSet(LocationId='HOTELL%20ARISTON')/LocationAddressArray(LocationId='HOTELL%20ARISTON',AddressInfoId=923)
//Should be:
Get LocationHandling.svc/LocationSet(LocationId='HOTELL%20ARISTON')/LocationAddressArray(LocationId='HOTELL%20ARISTON',AddressInfoId={$addressInfoId})
i1=123; s1="abc"; n1=null; b1=true;
Hello {$i1} World => Hello 123 World
Hello {$s1} World => Hello abc World
Hello {$n1} World => Hello null World
Hello {$b1} World => Hello true World

The expressions are evaluated using a C# syntax. See below.

For every command; command arguments and request payload is scanned for substitution markers and markers are replaced before command is executed. The surrounding markers are removed.

Some simple substitution examples:

Hello {$ 2 + 4} World
Hello "{$ 2 + 4}" World
Hello {$ "ABC" + "DEF"} World
Hello {   $Random()    } World

After:

Hello 6 World
Hello "6" World
Hello ABCDEF World
Hello 465872 World

Note from examples above. Expressions are first matched using substitution markers, then evaluated and finally replaced (with markers). This means that surrounding quotes are not part of expression evaluation. Also any whitespace within expression follow C# whitespace rules.

A substitution expression can also reference identifiers created from previous calls or Eval commands. See variables

Variable handling

Substitution expressions are powerful when combined with variable handling. Variables are stored results of previous call responses, input from other scripts or as result of Eval commands.

Variables are used in substitution expressions for server invokes or Assert.

Server calls

Server call commands have an Into (or Using) clause to reference variable store. Only JSON is supported as response content from server calls.

JSON content

Consider a JSON response like below stored in a result1 variable

{
    "s1": "Hello",
    "i1": 3.14,
    "b1": false,
    "o1" : {
        "s2": "World",
        "i2": 2.72
    },
    "a1" : [
        {
            "s2": "Happy",
            "i2": 2.72
        },
        {
            "s2": "Days",
            "i2": 1.41
        }
    ]
}

JSON content can be retrieved and used in the following ways.

  • result1.s1 - returns "Hello"
  • result1.i1 - returns 3.14
  • result1.o1.s2 - returns "World" (from the embedded object in o1)
  • result1.o1.i2 - returns 2.72
  • result1.a1.Items(0).s2- returns "Happy" (from first element in array a1)
  • result1.a1.Items(1).i2- returns 1.41. Notice the Items(index) function to access array elements.
  • result1.a1.ItemsCount()- returns 2. Counts the number of elements in array in a1.
  • Convert.ToDouble(result1.o1.i2) == 2.72 - returns true. Note that JSON results are internally treated as System.Object. Therefore Convert.ToDouble() is needed

Variables and substitutions

Variables are mostly used in substitution expressions or Eval/Assert commands.

Consider a substitution expression using the result1 as described above * {$Convert.ToDouble(result1.i1) + 10}- returns 13.14 * {$Convert.ToBoolean(result1.b1) == false}- returns true (since b1 is false) * {$result1.s1.ToString() + result1.o1.s2.ToString()} - returns "HelloWorld" * {$Convert.ToString(result1.s1) + " " + Convert.ToString(result.o1.s2)} - returns "Hello World" * {$Convert.ToDouble(result1.a1.Items(0).i2) + Convert.ToDouble(result1.a1.Items(1).i2)} - returns 4.13

The JSON results are handled as .NET Dynamic types and the DynamicExpresso engine can only set the .NET Type to System.Object. And since System.Object lacks comparison, math operators, any result from server call used for calculations requires to be converted into a "real" type, like Boolean, Double etc. Therefore Convert.ToXyz() functions are needed.

Variables and Eval/Assert commands

Eval and Assert commands are built to execute expressions. However, any command arguments are passed through the substitution engine. Therefore is parameter substitution seldom needed in these commands

So these two Eval will provide the same result. Assume a variable result1 with JSON content as defined above

Eval {$result1.s1}{$result.o1.s2} Into someVar

is exactly the same as

Eval {$result1.s1.ToString() + result1.o1.s2.ToString()} Into someVar

The first uses two substitutions and evaluates two expressions whereas the second evals one larger expression.

Variables and Server commands

Server call commands like Create, Modify etc can use variables both in the URL part and in the body payload. All expressions are evaluated and replaced before the server call is performed.

Example before substitution (and using the result1 variable contains the JSON defection as described above)

Post SomeService.svc/SomeEntitySet(SomeVal='{$result1.s1}')
{
    "someStr": "{$result1.o1.s2}",
    "someNum": {$result1.i1},
    "someBool": {$result1.b1}
}

will call the URL http://server:port/.../SomeService.svc/SomeEntitySet(SomeVal='Hello') with the reqeust body

{
    "someStr": "World",
    "someNum": 3.14,
    "someBool": false
}

Input variables

Input variables (sent between scripts using the Call command) is accessible through the same variable name as other variables. Input variables are assigned before script is executed

NOTE! This behavior might change in future to support more formal input handling/validation.

More examples

Example of variable substitution, assuming a variable named v1 with value "abc", v2 with value "def" and v3 with value 10.

Before substitution

Hello {$v1} World
Hello "{$v1}" World
Hello "{$v1}" {$v2} World
Hello "{$v1 + v2}" World
Hello "{$Random()}" World
Hello "{$Today().AddDays(1)}" World

After substitution

Hello abc World
Hello "abc" World
Hello "abc" def World
Hello "abcdef" World
Hello "435345" World
Hello "2019-10-22T00:00:00" World

A JSON based example

Before substitution

{
    "param1": "{$v1}",
    "{$v2}": {$v3}
}

After substitution

{
    "param1": "abc",
    "def": 10
}

Example using an evaluated variable for use in a create call.

Eval "1" Into contract
Eval Random() into rndVal
Post DockCodesHandling.svc/PurchaseDockCodes
{
    "Contract": "{$contract}",
    "DockCode": "Dock{$rndVal}",
    "Description": "Main Gate - [$rndVal}"
}

Example using variable to validate response from a Get request (continue from previous example)

Get DockCodesHandling.svc/PurchaseDockCodes(Contract='1',DockCode='DockA') Into getResult1
Assert "{$getResult1.Description}" == "Main Gate - [$rndVal}"

Connecting as different users

In some tests, there is a need to connect as different users. For this purpose, the following syntax can be used:

Connect <UserId>:<Password>

If the Password is the same as the UserId it can be omitted.

If no user ID is specified then the Connect command will re-connect using the credentials given when Script-A-Rest starts

User IDs and Passwords are case sensitive.

Examples

// Connect as known user john, with password also being john
Connect john
Get FrameworkServices.svc/GetCurrentUserInformation() Into user
Assert user.Name == "John Doe"

// Connect as user lisa using the password supplied on the command line / configuration
Connect lisa:{$globalconfig.password}

// Re-connect using the credentials given when starting Script-A-Rest
Connect

Accessing configuration and command line parameters

The global configuration, which is the union of command line parameters and the parameter specified in the config file, can be accessed from inside scripts using the globalconfig variable. You can use globalconfig in much the same way as you use the input variable.

globalconfig will contain members for the standard configuration parameters such as serverurl, username and password along with any additional command line parameters you specify. Please note that these member attributes are always written in lowercase in the scripts, regardless of the casing in the config file or command line parameter.

Example

Print "Script is running against environment with base url: " + globalconfig.serverurl

Eval globalconfig.username Into userIdentity

Create UserHandling.svc/SetUserPassword 
{
    "Username"        : {%userIdentity},
    "Password"        : {%globalconfig.password},
    "PasswordConfirm" : {%globalconfig.password},
    "Temporary"       : false
}

Print "Value of my custom command line prameter 'MyCmDLineParam' is: " + globalconfig.mycmdlineparam

Expression evaluator

The expression evaluator is based on DynamicExpresso

The following operators are allowed (copied from link above)

Statements can be written using a subset of the C# syntax. Here you can find a list of the supported expressions:

Operators

Supported operators:

Category Operators
Primary x.y f(x) a[x] new typeof
Unary + - ! (T)x
Multiplicative * / %
Additive + -
Relational and type testing < > <= >= is as
Equality == !=
Logical AND &
Logical OR |
Logical XOR ^
Conditional AND &&
Conditional OR ||
Conditional ?:
Assignment =
Null coalescing ??

Operators precedence is respected following C# rules (Operator precedence and associativity).

Some operators, like the assignment operator, can be disabled for security reason.

Category Operators
Constants true false null
Numeric f m
String/char "" ''

The following character escape sequences are supported inside string or char literals:

  • \' - single quote, needed for character literals
  • \" - double quote, needed for string literals
  • \\ - backslash
  • \0 - Unicode character 0
  • \a - Alert (character 7)
  • \b - Backspace (character 8)
  • \f - Form feed (character 12)
  • \n - New line (character 10)
  • \r - Carriage return (character 13)
  • \t - Horizontal tab (character 9)
  • \v - Vertical quote (character 11)

Functions

Supported functions

Date retrieval

  • Now() - Now as DateTime
  • Today() - Todays date as DateTime
  • NextMonday() - Next mondays date as DateTime
  • NextMonthFirstMonday() - Next month first mondays date as DateTime
  • NextYearFirstMonday() - Next year first mondays date as DateTime
  • PreviousMonday() - Previous mondays date as DateTime
  • PreviousMonthFirstMonday() - Previous month first mondays date as DateTime
  • PreviousYearFirstMonday() - Previous year first mondays date as DateTime
  • MonthName(<DateTime>) - Return Month Name of the given date
  • DayName(<DateTime>) - Return Day Name of the given date
  • OffsetYearFirstMonday(<int> offset) - Return first mondays date of the given offset year
  • AddWorkingDays(<DateTime>,<int> NumberOfDaysToAdd) - Return the DateTime after adding the number of specified working days
  • WorkingDaysBetween(<DateTime>,<DateTime>) - Return number of working days between two Dates

Date conversion

  • YearString(<DateTime>) - Return as "yyyy"

  • MonthString(<DateTime>) - Return as "MM"

  • DayString(<DateTime>) - Return as "dd"

  • DateString(<DateTime>) - Return as "yyyy-MM-dd"

  • TimeString(<DateTime>) - Return as "yyyy-MM-ddTHH:mm:ssZ"

  • TimeZoneString(<DateTime>) - Return as "yyyy-MM-ddTHH:mm:ssK"

  • ToUtc(<DateTime>,<string> TimeZone) - Return given converted into UTC time from the given TimeZone

  • ToUtcFromTimeZoneString(<string> DateTimeString,<string> TimeZone) - Return given DateTimeString (yyyy-MM-ddTHH:mm:ssZ) converted into UTC time from the given TimeZone Example:

ToUtcFromTimeZoneString("2024-07-11T08:00:00Z", "UTC")
ToUtcFromTimeZoneString("2024-06-05T13:08:58+05:30", "Colombo")`

Number functions

  • Random() - Return random value as number
  • RandomWithRange(<int> min,<int> max) - Return random value as number within a range specified with min and max.

Variable existande testing functions

  • InputExists(<string> variableName) - Return true variableName is passed as input to a script.
  • VariableExists(<string> variableName) - Return true variableName is created as local variable within a script.

Meta data

Meta data is mandatory for test scripts and shall be located in the top of the script. No blanc rows are allowed above the meta data. Note that meta data is case sensitive and will impact how test reports are generated. The following meta data exists.

  • type
  • owner
  • mode

type

Mandatory for all scripts and can have the following values:

type Allowed to call scripts of type
Test Collection Test Collection, Test Suite
Test Suite Test Case
Test Case Test Util
Test Data Test Util
Test Util Test Util

owner

Mandatory for all scripts and can have any value.

mode

Mandatory for scripts with type Test Case

mode Description
Standalone Test Case executes independent
Dependent Test Case is depending on other Test Cases

Meta Data example:

---
type: Test Case
owner: Manufacturing
mode: Standalone
---

Add Header to Request

When using server calls (Create, Post, Get, Modify, Patch, ModifyClob, Delete, Batch .etc) predefine headers can be added to the request.

This can be done by adding a Include HeaderKey, HeaderKey parameter in the server commands. Users can only use headers which are listed in Header Keys.

This Headers can be passed from one script to another script by using the Call command and ExecuteCsv command. The Util file can access the headers passed from the caller script using Include {$header} parameter. $header is a special substitute variable that can be used to access the headers passed from the caller script.

Add Header to Http Request Example

Option 1: Add Header with default value

Example : Add TimeZoneHeader to the request, default value of TimeZoneHeader is "true"

Get TimestampTest.svc/TZAwareByLuQueries?$filter=Birthday eq {$input.TimeStampKey}  Include TimeZoneHeader Into result

Option 2: Add Header with a value Example : Add TimeZoneHeader to the request with a value, TimeZoneHeader supports values are "true/false".

Get TimestampTest.svc/TZAwareByLuQueries?$filter=Birthday eq {$input.TimeStampKey}  Include TimeZoneHeader="false" Into result

Option 3: Add Header by referring Call script command header definition Example : Add TimeZoneHeader to the request by referring Call script command header definition, Headers can be referred by $header variable.

Test Case:

Call ./util/GetTimestampTest.md Include TimeZoneHeader="true" Into result

Util Script: GetTimestampTest.md

Get TimestampTest.svc/TZAwareByLuQueries?$filter=Birthday eq {$input.TimeStampKey} Include {$header} Into result

Note: In TAR script TimeStamp attributes in the request/response always need to be in UTC date time format (yyyy-MM-ddTHH:mm:ssZ). So when you are adding a timestamp attribute to the request, you should use TimeString or TimeZoneString to format DateTime object to UTC format date time string.

Example:

Let's consider that there is a Entity called TZAwareEntity which has TIMESTAMP fields PaymentDateTime and EntryDateTime. In that entity TimestampTZRef annotation is used to specify the Time Zone aware fields.

codegenproperties {
   TimestampTZRef "server";
}

Since that entity is annotated as server, all the TIMESTAMP fields Date Time will be save in the database from database Time zone. OData provider will do the date time fields conversion to server time when Time zone header(X-Ifs-Time-Zone-Aware-Request) is true, then in the response server time will convert into the UTC Date time accordingly.

Util Script: CreateTZAwareEntity.mkd

Let's create a Util script to create records in TZAwareEntity.

Create TimestampTest.svc/TZAwareEntities  Include {$header} Into result
{
    "PaymentDateTime":"{$input.PaymentDateTime}",
    "EntryDateTime":"{$input.EntryDateTime}"
}
Output result

Util Script: GetTZAwareEntity.mkd

Let's create a Util script to get records in TZAwareEntity.

Get TimestampTest.svc/TZAwareEntities?$filter=PaymentDateTime eq {$input.PaymentDateTime}  Include {$header} Into result
Output result

Test Case Script: TZAwareEntityTestCase.mkd

Let's create a Test Case script to create records and get records in TZAwareEntity by referring the Util scripts. When we are sending timestamp attributes in request we must add those timestamp to request payload in UTC time zone format (yyyy-MM-ddTHH:mm:ssZ).

Eval TimeZoneString(ToUtc(Now(), "Asia/Colombo")) Into paymentDateTime  

Eval PreviousYearFirstMonday() Into entryDateTime

Eval TimeZoneString(ToUtc(entryDateTime, null)) Into entryDateTimeStr  // ToUtc will convert given date time from running instance time zone to UTC date time. since runqning instance time zone is Colombo, it is same as above line. 

Call  ../util/CreateTZAwareEntity.mkd With Json Include TimeZoneHeader Into createResult
{
    "PaymentDateTime":"{$paymentDateTime}",
    "EntryDateTime":"{$entryDateTimeStr}"
}

Call  ../util/GetTZAwareEntity.mkd Include TimeZoneHeader Into getResult

Assert "{$getResult.value.Items(0).PaymentDateTime}" == "{$paymentDateTime}"

Test Case Script: TZUnAwareEntityTestCase.mkd

Let's create a Test Case script to create records and get records in TZAwareEntity by referring the Util scripts, but Time zone header(X-Ifs-Time-Zone-Aware-Request) is false. So no Date Time conversion will happen in the server side.

Eval TimeString(Now()) Into paymentDateTime

Eval PreviousYearFirstMonday() Into entryDateTime

Eval TimeString(entryDateTime) Into entryDateTimeStr

Call  ../util/CreateTZAwareEntity.mkd With Json Include TimeZoneHeader="false" Into createResult
{
    "PaymentDateTime":"{$paymentDateTime}",
    "EntryDateTime":"{$entryDateTimeStr}"
}

Call  ../util/GetTZAwareEntity.mkd Include TimeZoneHeader="false" Into getResult

Assert "{$getResult.value.Items(0).PaymentDateTime}" == "{$paymentDateTime}"

Header Keys

HeaderKey Header Name Value Description
TimeZoneHeader X-Ifs-Time-Zone-Aware-Request true / false This header is used to specify the HTTP request is Time Zone aware or not. If the header value is true, then all the TimeStamp attribute values in the request/response are Time Zone aware.
If the header value is false, then all the TimeStamp attribute values in the request/response are not Time Zone aware. By referring the value of this header OData provider will do the conversion to TimeStamp attribute values of request/response(Current ODP converts TimeStamp attribute values to UTC).
Note: TimeStamp attribute values in the Database will be base on the configuration of entity, structure, action etc.