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¶
- Expect Error on server calls
- Substitution handling
- Variable handling
- Connecting as different users
- Accessing configuration and command line parameters
- Expression evaluator
- Meta data
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"}
}
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
Into
with Delete
to get the response)
Delete DockCodesHandling.svc/PurchaseDockCodes(Contract='1',DockCode='DockA') Using getResult1 Into response
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"
}
{
"s1": "Hello",
"s2": "Universe",
"n1": "3.14"
}
Eval "Hello" Into s1
ApplyJSON Into test3 When VariableExists("s1")
{
"s1": {%s1}
}
{
"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")
{
"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
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.14result1.o1.s2
- returns "World" (from the embedded object in o1)result1.o1.i2
- returns 2.72result1.a1.Items(0).s2
- returns "Happy" (from first element in array a1)result1.a1.Items(1).i2
- returns 1.41. Notice theItems(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 DateTimeToday()
- Todays date as DateTimeNextMonday()
- Next mondays date as DateTimeNextMonthFirstMonday()
- Next month first mondays date as DateTimeNextYearFirstMonday()
- Next year first mondays date as DateTimePreviousMonday()
- Previous mondays date as DateTimePreviousMonthFirstMonday()
- Previous month first mondays date as DateTimePreviousYearFirstMonday()
- Previous year first mondays date as DateTimeMonthName(<DateTime>)
- Return Month Name of the given dateDayName(<DateTime>)
- Return Day Name of the given dateOffsetYearFirstMonday(<int> offset)
- Return first mondays date of the given offset yearAddWorkingDays(<DateTime>,<int> NumberOfDaysToAdd)
- Return the DateTime after adding the number of specified working daysWorkingDaysBetween(<DateTime>,<DateTime>)
- Return number of working days between two Dates
Date conversion
-
YearString(<DateTime>)
- Returnas "yyyy" -
MonthString(<DateTime>)
- Returnas "MM" -
DayString(<DateTime>)
- Returnas "dd" -
DateString(<DateTime>)
- Returnas "yyyy-MM-dd" -
TimeString(<DateTime>)
- Returnas "yyyy-MM-ddTHH:mm:ssZ" -
TimeZoneString(<DateTime>)
- Returnas "yyyy-MM-ddTHH:mm:ssK" -
ToUtc(<DateTime>,<string> TimeZone)
- Return givenconverted into UTC time from the given TimeZone -
ToUtcFromTimeZoneString(<string> DateTimeString,<string> TimeZone)
- Return givenDateTimeString
(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 numberRandomWithRange(<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. |