Support OVSDB entity name format in Phosphorus
[integration/test.git] / csit / libraries / TemplatedRequests.robot
1 *** Settings ***
2 Documentation     Resource for supporting http Requests based on data stored in files.
3 ...
4 ...               Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
5 ...
6 ...               This program and the accompanying materials are made available under the
7 ...               terms of the Eclipse Public License v1.0 which accompanies this distribution,
8 ...               and is available at http://www.eclipse.org/legal/epl-v10.html
9 ...
10 ...
11 ...               The main strength of this library are *_As_*_Templated keywords
12 ...               User gives a path to directory where files with templates for URI
13 ...               and XML (or JSON) data are present, and a mapping with substitution to make;
14 ...               the keywords will take it from there.
15 ...               Mapping can be given as a dict object, or as its json text representation.
16 ...               Simple example (tidy insists on single space where 4 spaces should be):
17 ...               TemplatedRequests.Put_As_Json_Templated folder=${VAR_BASE}/person mapping={"NAME":"joe"}
18 ...               TemplatedRequests.Get_As_Json_Templated folder=${VAR_BASE}/person mapping={"NAME":"joe"} verify=True
19 ...
20 ...               In that example, we are PUTting "person" data with specified value for "NAME" placeholder.
21 ...               We are not verifying PUT response (probably empty string which is not a valid JSON),
22 ...               but we are issuing GET (same URI) and verifying the repsonse matches the same data.
23 ...               Both lines are returning text response, but in the example we are not saving it into variable.
24 ...
25 ...               Optionally, *_As_*_Templated keywords call verification of response.
26 ...               There are separate Verify_* keywords, for users who use intermediate processing.
27 ...               For JSON responses, there is a support for normalizing.
28 ...               *_Templated keywords without As allow more customization at cost of more arguments.
29 ...               *_Uri keywords do not use templates, but may be useful in general,
30 ...               perhaps for users who call Resolve_Text_* keywords.
31 ...               *_As_*_Uri are the less flexible but less argument-heavy versions of *_Uri keywords.
32 ...
33 ...               This resource supports generating data with simple lists.
34 ...               ${iterations} argument control number of items, "$i" will be substituted
35 ...               automatically (not by the provided mapping) with integers starting with ${iter_start} (default 1).
36 ...               For example "iterations=2 iter_start=3" will create items with i=3 and i=4.
37 ...
38 ...               This implementation relies on file names to distinguish data.
39 ...               Each file is expected to end in newline, compiled data has final newline removed.
40 ...               Here is a table so that users can create their own templates:
41 ...               location.uri: Template with URI.
42 ...               data.xml: Template with XML data to send, or GET data to expect.
43 ...               data.json: Template with JSON data to send, or GET data to expect.
44 ...               post_data.xml: Template with XML data to POST, (different from GET response).
45 ...               post_data.json: Template with JSON data to POST, (different from GET response).
46 ...               response.xml: Template with PUT or POST XML response to expect.
47 ...               response.json: Template with PUT or POST JSON response to expect.
48 ...               *.prolog.*: Temlate with data before iterated items.
49 ...               *.item.*: Template with data piece corresponding to one item.
50 ...               *.epilog.*: Temlate with data after iterated items.
51 ...
52 ...               One typical use of this Resource is to make runtime changes to ODL configuration.
53 ...               Different ODL parts have varying ways of configuration,
54 ...               this library affects only the Config Subsystem way.
55 ...               Config Subsystem has (except for Java APIs mostly available only from inside of ODL)
56 ...               a NETCONF server as its publicly available entry point.
57 ...               Netconf-connector feature makes this netconf server available for RESTCONF calls.
58 ...               Be careful to use appropriate feature, odl-netconf-connector* does not work in cluster.
59 ...
60 ...               This Resource currently calls RequestsLibrary directly,
61 ...               so it does not work with AuthStandalone or similar.
62 ...               This Resource does not maintain any internal Sessions.
63 ...               If caller does not provide any, session with alias "default" is used.
64 ...               There is a helper Keyword to create the "default" session.
65 ...               The session used is assumed to have most things pre-configured appropriately,
66 ...               which includes auth, host, port and (lack of) base URI.
67 ...               It is recommended to have everything past port (for example /restconf) be defined
68 ...               not in the session, but in URI data of individual templates.
69 ...               Headers are set in Keywords appropriately. Http session's timout is configurable
70 ...               both on session level (where it becomes a default value for requests) and on request
71 ...               level (when present, it overrides the session value). To override the default
72 ...               value keywords' http_timeout parameter may be used.
73 ...
74 ...               These Keywords contain frequent BuiltIn.Log invocations,
75 ...               so they are not suited for scale or performance suites.
76 ...               And as usual, performance tests should use specialized utilities,
77 ...               as Robot in general and this Resource specifically will be too slow.
78 ...
79 ...               As this Resource makes assumptions about intended headers,
80 ...               it is not flexible enough for suites specifically testing Restconf corner cases.
81 ...               Also, list of allowed http status codes is quite rigid and broad.
82 ...
83 ...               Rules for ordering Keywords within this Resource:
84 ...               1. User friendlier Keywords first.
85 ...               2. Get, Put, Post, Delete, Verify.
86 ...               3. Within class of equally usable, use order in which a suite would call them.
87 ...               4. Higher-level Keywords first.
88 ...               5. Json before Xml.
89 ...               Motivation: Users read from the start, so it is important
90 ...               to offer them the better-to-use Keywords first.
91 ...               https://wiki.opendaylight.org/view/Integration/Test/Test_Code_Guidelines#Keyword_ordering
92 ...               In this case, templates are nicer that raw data,
93 ...               *_As_* keywords are better than messing wth explicit header dicts,
94 ...               Json is less prone to element ordering issues.
95 ...               PUT does not fail on existing element, also it does not allow
96 ...               shortened URIs (container instead keyed list element) as Post does.
97 ...
98 ...               TODO: Add ability to override allowed status codes,
99 ...               so that negative tests do not need to parse the failure message.
100 ...
101 ...               TODO: Migrate suites to this Resource and remove *ViaRestconf Resources.
102 ...
103 ...               TODO: Currently the verification step is only in *_As_*_Templated keywords.
104 ...               It could be moved to "non-as" *_Templated ones,
105 ...               but that would take even more horizontal space. Is it worth doing?
106 ...
107 ...               TODO: Should iterations=0 be supported for JSON (remove [])?
108 ...
109 ...               TODO: Currently, ${ACCEPT_EMPTY} is used for JSON-expecting requests.
110 ...               perhaps explicit ${ACCEPT_JSON} will be better, even if it sends few bytes more?
111 Library           Collections
112 Library           OperatingSystem
113 Library           String
114 Library           RequestsLibrary
115 Library           ${CURDIR}/norm_json.py
116 Resource          ${CURDIR}/../variables/Variables.robot
117
118 *** Variables ***
119 # TODO: Make the following list more narrow when streams without Bug 2594 fix (up to beryllium) are no longer used.
120 @{ALLOWED_DELETE_STATUS_CODES}    ${200}    ${201}    ${204}    ${404}    # List of integers, not strings. Used by DELETE if the resource may be not present.
121 @{ALLOWED_STATUS_CODES}    ${200}    ${201}    ${204}    # List of integers, not strings. Used by both PUT and DELETE (if the resource should have been present).
122 @{DATA_VALIDATION_ERROR}    ${400}    # For testing mildly negative scenarios where ODL reports user error.
123 @{DELETED_STATUS_CODES}    ${404}    ${409}    # List of integers, not strings. Used by DELETE if the resource may be not present.
124 # TODO: Add option for delete to require 404.
125 @{INTERNAL_SERVER_ERROR}    ${500}    # Only for testing severely negative scenarios where ODL cannot recover.
126 @{KEYS_WITH_BITS}    op    # the default list with keys to be sorted when norm_json libray is used
127 @{NO_STATUS_CODES}
128 @{UNAUTHORIZED_STATUS_CODES}    ${401}    # List of integers, not strings. Used in Keystone Authentication when the user is not authorized to use the requested resource.
129
130 *** Keywords ***
131 Create_Default_Session
132     [Arguments]    ${url}=http://${ODL_SYSTEM_IP}:${RESTCONFPORT}    ${auth}=${AUTH}    ${timeout}=${DEFAULT_TIMEOUT_HTTP}    ${max_retries}=0
133     [Documentation]    Create "default" session to ${url} with authentication and connection parameters.
134     ...    This Keyword is in this Resource only so that user do not need to call RequestsLibrary directly.
135     RequestsLibrary.Create_Session    alias=default    url=${url}    auth=${auth}    timeout=${timeout}    max_retries=${max_retries}
136
137 Get_As_Json_Templated
138     [Arguments]    ${folder}    ${mapping}={}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
139     ...    ${http_timeout}=${EMPTY}    ${log_response}=True    ${iter_j_offset}=0
140     [Documentation]    Add arguments sensible for JSON data, return Get_Templated response text.
141     ...    Optionally, verification against JSON data (may be iterated) is called.
142     ...    Only subset of JSON data is verified and returned if JMES path is specified in
143     ...    file ${folder}${/}jmespath.expr.
144     ${response_text} =    Get_Templated    folder=${folder}    mapping=${mapping}    accept=${ACCEPT_EMPTY}    session=${session}    normalize_json=True
145     ...    http_timeout=${http_timeout}    log_response=${log_response}
146     BuiltIn.Run_Keyword_If    ${verify}    Verify_Response_As_Json_Templated    response=${response_text}    folder=${folder}    base_name=data    mapping=${mapping}
147     ...    iterations=${iterations}    iter_start=${iter_start}    iter_j_offset=${iter_j_offset}
148     [Return]    ${response_text}
149
150 Get_As_Xml_Templated
151     [Arguments]    ${folder}    ${mapping}={}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
152     ...    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
153     [Documentation]    Add arguments sensible for XML data, return Get_Templated response text.
154     ...    Optionally, verification against XML data (may be iterated) is called.
155     ${response_text} =    Get_Templated    folder=${folder}    mapping=${mapping}    accept=${ACCEPT_XML}    session=${session}    normalize_json=False
156     ...    http_timeout=${http_timeout}
157     BuiltIn.Run_Keyword_If    ${verify}    Verify_Response_As_Xml_Templated    response=${response_text}    folder=${folder}    base_name=data    mapping=${mapping}
158     ...    iterations=${iterations}    iter_start=${iter_start}    iter_j_offset=${iter_j_offset}
159     [Return]    ${response_text}
160
161 Put_As_Json_Templated
162     [Arguments]    ${folder}    ${mapping}={}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
163     ...    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
164     [Documentation]    Add arguments sensible for JSON data, return Put_Templated response text.
165     ...    Optionally, verification against response.json (no iteration) is called.
166     ...    Only subset of JSON data is verified and returned if JMES path is specified in
167     ...    file ${folder}${/}jmespath.expr.
168     ${response_text} =    Put_Templated    folder=${folder}    base_name=data    extension=json    accept=${ACCEPT_EMPTY}    content_type=${HEADERS_YANG_JSON}
169     ...    mapping=${mapping}    session=${session}    normalize_json=True    endline=${\n}    iterations=${iterations}    iter_start=${iter_start}
170     ...    http_timeout=${http_timeout}    iter_j_offset=${iter_j_offset}
171     BuiltIn.Run_Keyword_If    ${verify}    Verify_Response_As_Json_Templated    response=${response_text}    folder=${folder}
172     ...    base_name=response    mapping=${mapping}    iter_j_offset=${iter_j_offset}
173     [Return]    ${response_text}
174
175 Put_As_Xml_Templated
176     [Arguments]    ${folder}    ${mapping}={}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
177     ...    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
178     [Documentation]    Add arguments sensible for XML data, return Put_Templated response text.
179     ...    Optionally, verification against response.xml (no iteration) is called.
180     # In case of iterations, we use endlines in data to send, as it should not matter and it is more readable.
181     ${response_text} =    Put_Templated    folder=${folder}    base_name=data    extension=xml    accept=${ACCEPT_XML}    content_type=${HEADERS_XML}
182     ...    mapping=${mapping}    session=${session}    normalize_json=False    endline=${\n}    iterations=${iterations}    iter_start=${iter_start}
183     ...    http_timeout=${http_timeout}    iter_j_offset=${iter_j_offset}
184     BuiltIn.Run_Keyword_If    ${verify}    Verify_Response_As_Xml_Templated    response=${response_text}    folder=${folder}
185     ...    base_name=response    mapping=${mapping}    iter_j_offset=${iter_j_offset}
186     [Return]    ${response_text}
187
188 Post_As_Json_Templated
189     [Arguments]    ${folder}    ${mapping}={}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
190     ...    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${explicit_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
191     [Documentation]    Add arguments sensible for JSON data, return Post_Templated response text.
192     ...    Optionally, verification against response.json (no iteration) is called.
193     ...    Only subset of JSON data is verified and returned if JMES path is specified in
194     ...    file ${folder}${/}jmespath.expr.
195     ...    Response status code must be one of values from ${explicit_status_codes} if specified or one of set
196     ...    created from all positive HTTP status codes together with ${additional_allowed_status_codes}.
197     ${response_text} =    Post_Templated    folder=${folder}    base_name=data    extension=json    accept=${ACCEPT_EMPTY}    content_type=${HEADERS_YANG_JSON}
198     ...    mapping=${mapping}    session=${session}    normalize_json=True    endline=${\n}    iterations=${iterations}    iter_start=${iter_start}
199     ...    additional_allowed_status_codes=${additional_allowed_status_codes}    explicit_status_codes=${explicit_status_codes}    http_timeout=${http_timeout}
200     ...    iter_j_offset=${iter_j_offset}
201     BuiltIn.Run_Keyword_If    ${verify}    Verify_Response_As_Json_Templated    response=${response_text}    folder=${folder}
202     ...    base_name=response    mapping=${mapping}    iter_j_offset=${iter_j_offset}
203     [Return]    ${response_text}
204
205 Post_As_Json_Rfc8040_Templated
206     [Arguments]    ${folder}    ${mapping}={}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
207     ...    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${explicit_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
208     [Documentation]    Add arguments sensible for JSON data, return Post_Templated response text.
209     ...    Optionally, verification against response.json (no iteration) is called.
210     ...    Only subset of JSON data is verified and returned if JMES path is specified in
211     ...    file ${folder}${/}jmespath.expr.
212     ...    Response status code must be one of values from ${explicit_status_codes} if specified or one of set
213     ...    created from all positive HTTP status codes together with ${additional_allowed_status_codes}.
214     ...    RFC8040 defines RESTCONF protocol, for configuring data defined in YANG version 1
215     ...    or YANG version 1.1, using the datastore concepts defined in NETCONF.
216     ${response_text} =    Post_Templated    folder=${folder}    base_name=data    extension=json    accept=${ACCEPT_EMPTY}    content_type=${HEADERS_YANG_RFC8040_JSON}
217     ...    mapping=${mapping}    session=${session}    normalize_json=True    endline=${\n}    iterations=${iterations}    iter_start=${iter_start}
218     ...    additional_allowed_status_codes=${additional_allowed_status_codes}    explicit_status_codes=${explicit_status_codes}    http_timeout=${http_timeout}
219     ...    iter_j_offset=${iter_j_offset}
220     BuiltIn.Run_Keyword_If    ${verify}    Verify_Response_As_Json_Templated    response=${response_text}    folder=${folder}    base_name=response    mapping=${mapping}
221     ...    iter_j_offset=${iter_j_offset}
222     [Return]    ${response_text}
223
224 Post_As_Xml_Templated
225     [Arguments]    ${folder}    ${mapping}={}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
226     ...    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${explicit_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
227     [Documentation]    Add arguments sensible for XML data, return Post_Templated response text.
228     ...    Optionally, verification against response.xml (no iteration) is called.
229     # In case of iterations, we use endlines in data to send, as it should not matter and it is more readable.
230     ${response_text} =    Post_Templated    folder=${folder}    base_name=data    extension=xml    accept=${ACCEPT_XML}    content_type=${HEADERS_XML}
231     ...    mapping=${mapping}    session=${session}    normalize_json=False    endline=${\n}    iterations=${iterations}    iter_start=${iter_start}
232     ...    additional_allowed_status_codes=${additional_allowed_status_codes}    explicit_status_codes=${explicit_status_codes}    http_timeout=${http_timeout}
233     ...    iter_j_offset=${iter_j_offset}
234     BuiltIn.Run_Keyword_If    ${verify}    Verify_Response_As_Xml_Templated    response=${response_text}    folder=${folder}    base_name=response    mapping=${mapping}
235     ...    iter_j_offset=${iter_j_offset}
236     [Return]    ${response_text}
237
238 Delete_Templated
239     [Arguments]    ${folder}    ${mapping}={}    ${session}=default    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}    ${location}=location
240     [Documentation]    Resolve URI from folder, issue DELETE request.
241     ${uri} =    Resolve_Text_From_Template_Folder    folder=${folder}    base_name=${location}    extension=uri    mapping=${mapping}
242     ${response_text} =    Delete_From_Uri    uri=${uri}    session=${session}    additional_allowed_status_codes=${additional_allowed_status_codes}    http_timeout=${http_timeout}
243     [Return]    ${response_text}
244
245 Verify_Response_As_Json_Templated
246     [Arguments]    ${response}    ${folder}    ${base_name}=response    ${mapping}={}    ${iterations}=${EMPTY}    ${iter_start}=1    ${iter_j_offset}=0
247     [Documentation]    Resolve expected JSON data, should be equal to provided \${response}.
248     ...    JSON normalization is used, endlines enabled for readability.
249     Verify_Response_Templated    response=${response}    folder=${folder}    base_name=${base_name}    extension=json    mapping=${mapping}    normalize_json=True
250     ...    endline=${\n}    iterations=${iterations}    iter_start=${iter_start}    iter_j_offset=${iter_j_offset}
251
252 Verify_Response_As_Xml_Templated
253     [Arguments]    ${response}    ${folder}    ${base_name}=response    ${mapping}={}    ${iterations}=${EMPTY}    ${iter_start}=1    ${iter_j_offset}=0
254     [Documentation]    Resolve expected XML data, should be equal to provided \${response}.
255     ...    Endline set to empty, as this Resource does not support indented XML comparison.
256     Verify_Response_Templated    response=${response}    folder=${folder}    base_name=${base_name}    extension=xml    mapping=${mapping}    normalize_json=False
257     ...    endline=${EMPTY}    iterations=${iterations}    iter_start=${iter_start}    iter_j_offset=${iter_j_offset}
258
259 Get_As_Json_From_Uri
260     [Arguments]    ${uri}    ${session}=default    ${http_timeout}=${EMPTY}    ${log_response}=True
261     [Documentation]    Specify JSON headers and return Get_From_Uri normalized response text.
262     ${response_text} =    Get_From_Uri    uri=${uri}    accept=${ACCEPT_EMPTY}    session=${session}    normalize_json=True    http_timeout=${http_timeout}    log_response=${log_response}
263     [Return]    ${response_text}
264
265 Get_As_Xml_From_Uri
266     [Arguments]    ${uri}    ${session}=default    ${http_timeout}=${EMPTY}    ${log_response}=True
267     [Documentation]    Specify XML headers and return Get_From_Uri response text.
268     ${response_text} =    Get_From_Uri    uri=${uri}    accept=${ACCEPT_XML}    session=${session}    normalize_json=False    http_timeout=${http_timeout}    log_response=${log_response}
269     [Return]    ${response_text}
270
271 Put_As_Json_To_Uri
272     [Arguments]    ${uri}    ${data}    ${session}=default    ${http_timeout}=${EMPTY}
273     [Documentation]    Specify JSON headers and return Put_To_Uri normalized response text.
274     ...    Yang json content type is used as a workaround to RequestsLibrary json conversion eagerness.
275     ${response_text} =    Put_To_Uri    uri=${uri}    data=${data}    accept=${ACCEPT_EMPTY}    content_type=${HEADERS_YANG_JSON}    session=${session}
276     ...    normalize_json=True    http_timeout=${http_timeout}
277     [Return]    ${response_text}
278
279 Put_As_Xml_To_Uri
280     [Arguments]    ${uri}    ${data}    ${session}=default    ${http_timeout}=${EMPTY}
281     [Documentation]    Specify XML headers and return Put_To_Uri response text.
282     ${response_text} =    Put_To_Uri    uri=${uri}    data=${data}    accept=${ACCEPT_XML}    content_type=${HEADERS_XML}    session=${session}
283     ...    normalize_json=False    http_timeout=${http_timeout}
284     [Return]    ${response_text}
285
286 Post_As_Json_To_Uri
287     [Arguments]    ${uri}    ${data}    ${session}=default    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${explicit_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}
288     [Documentation]    Specify JSON headers and return Post_To_Uri normalized response text.
289     ...    Yang json content type is used as a workaround to RequestsLibrary json conversion eagerness.
290     ...    Response status code must be one of values from ${explicit_status_codes} if specified or one of set
291     ...    created from all positive HTTP status codes together with ${additional_allowed_status_codes}.
292     ${response_text} =    Post_To_Uri    uri=${uri}    data=${data}    accept=${ACCEPT_EMPTY}    content_type=${HEADERS_YANG_JSON}    session=${session}
293     ...    normalize_json=True    additional_allowed_status_codes=${additional_allowed_status_codes}    explicit_status_codes=${explicit_status_codes}    http_timeout=${http_timeout}
294     [Return]    ${response_text}
295
296 Post_As_Xml_To_Uri
297     [Arguments]    ${uri}    ${data}    ${session}=default    ${http_timeout}=${EMPTY}
298     [Documentation]    Specify XML headers and return Post_To_Uri response text.
299     ${response_text} =    Post_To_Uri    uri=${uri}    data=${data}    accept=${ACCEPT_XML}    content_type=${HEADERS_XML}    session=${session}
300     ...    normalize_json=False    http_timeout=${http_timeout}
301     [Return]    ${response_text}
302
303 Delete_From_Uri
304     [Arguments]    ${uri}    ${session}=default    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}
305     [Documentation]    DELETE resource at URI, check status_code and return response text..
306     BuiltIn.Log    ${uri}
307     ${response} =    BuiltIn.Run_Keyword_If    """${http_timeout}""" == """${EMPTY}"""    RequestsLibrary.Delete_Request    alias=${session}    uri=${uri}
308     ...    ELSE    RequestsLibrary.Delete_Request    alias=${session}    uri=${uri}    timeout=${http_timeout}
309     Check_Status_Code    ${response}    additional_allowed_status_codes=${additional_allowed_status_codes}
310     [Return]    ${response.text}
311
312 Resolve_Jmes_Path
313     [Arguments]    ${folder}
314     [Documentation]    Reads JMES path from file ${folder}${/}jmespath.expr if the file exists and
315     ...    returns the JMES path. Empty string is returned otherwise.
316     ${read_jmes_file} =    BuiltIn.Run Keyword And Return Status    OperatingSystem.File Should Exist    ${folder}${/}jmespath.expr
317     ${jmes_expression} =    Run Keyword If    ${read_jmes_file} == ${true}    OperatingSystem.Get_File    ${folder}${/}jmespath.expr
318     ${expression} =    BuiltIn.Set Variable If    ${read_jmes_file} == ${true}    ${jmes_expression}    ${EMPTY}
319     [Return]    ${expression}
320
321 Resolve_Volatiles_Path
322     [Arguments]    ${folder}
323     [Documentation]    Reads Volatiles List from file ${folder}${/}volatiles.list if the file exists and
324     ...    returns the Volatiles List. Empty string is returned otherwise.
325     ${read_volatiles_file} =    BuiltIn.Run Keyword And Return Status    OperatingSystem.File Should Exist    ${folder}${/}volatiles.list
326     Return From Keyword If    ${read_volatiles_file} == ${false}    ${EMPTY}
327     ${volatiles}=    OperatingSystem.Get_File    ${folder}${/}volatiles.list
328     ${volatiles_list}=    String.Split_String    ${volatiles}    ${\n}
329     [Return]    ${volatiles_list}
330
331 Get_Templated
332     [Arguments]    ${folder}    ${accept}    ${mapping}={}    ${session}=default    ${normalize_json}=False    ${http_timeout}=${EMPTY}    ${log_response}=True
333     [Documentation]    Resolve URI from folder, call Get_From_Uri, return response text.
334     ${uri} =    Resolve_Text_From_Template_Folder    folder=${folder}    base_name=location    extension=uri    mapping=${mapping}
335     ${jmes_expression} =    Resolve_Jmes_Path    ${folder}
336     ${volatiles_list}=    Resolve_Volatiles_Path    ${folder}
337     ${response_text} =    Get_From_Uri    uri=${uri}    accept=${accept}    session=${session}    normalize_json=${normalize_json}    jmes_path=${jmes_expression}
338     ...    http_timeout=${http_timeout}    keys_with_volatiles=${volatiles_list}    log_response=${log_response}
339     [Return]    ${response_text}
340
341 Put_Templated
342     [Arguments]    ${folder}    ${base_name}    ${extension}    ${content_type}    ${accept}    ${mapping}={}
343     ...    ${session}=default    ${normalize_json}=False    ${endline}=${\n}    ${iterations}=${EMPTY}    ${iter_start}=1    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
344     [Documentation]    Resolve URI and data from folder, call Put_To_Uri, return response text.
345     ${uri} =    Resolve_Text_From_Template_Folder    folder=${folder}    base_name=location    extension=uri    mapping=${mapping}
346     ${data} =    Resolve_Text_From_Template_Folder    folder=${folder}    base_name=${base_name}    extension=${extension}    mapping=${mapping}    endline=${endline}
347     ...    iterations=${iterations}    iter_start=${iter_start}    iter_j_offset=${iter_j_offset}
348     ${jmes_expression} =    Resolve_Jmes_Path    ${folder}
349     ${response_text} =    Put_To_Uri    uri=${uri}    data=${data}    content_type=${content_type}    accept=${accept}    session=${session}
350     ...    http_timeout=${http_timeout}    normalize_json=${normalize_json}    jmes_path=${jmes_expression}
351     [Return]    ${response_text}
352
353 Post_Templated
354     [Arguments]    ${folder}    ${base_name}    ${extension}    ${content_type}    ${accept}    ${mapping}={}
355     ...    ${session}=default    ${normalize_json}=False    ${endline}=${\n}    ${iterations}=${EMPTY}    ${iter_start}=1    ${additional_allowed_status_codes}=${NO_STATUS_CODES}
356     ...    ${explicit_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
357     [Documentation]    Resolve URI and data from folder, call Post_To_Uri, return response text.
358     ${uri} =    Resolve_Text_From_Template_Folder    folder=${folder}    base_name=location    extension=uri    mapping=${mapping}
359     ${data} =    Resolve_Text_From_Template_Folder    folder=${folder}    name_prefix=post_    base_name=${base_name}    extension=${extension}    mapping=${mapping}
360     ...    endline=${endline}    iterations=${iterations}    iter_start=${iter_start}    iter_j_offset=${iter_j_offset}
361     ${jmes_expression} =    Resolve_Jmes_Path    ${folder}
362     ${response_text} =    Post_To_Uri    uri=${uri}    data=${data}    content_type=${content_type}    accept=${accept}    session=${session}
363     ...    jmes_path=${jmes_expression}    normalize_json=${normalize_json}    additional_allowed_status_codes=${additional_allowed_status_codes}    explicit_status_codes=${explicit_status_codes}    http_timeout=${http_timeout}
364     [Return]    ${response_text}
365
366 Verify_Response_Templated
367     [Arguments]    ${response}    ${folder}    ${base_name}    ${extension}    ${mapping}={}    ${normalize_json}=False
368     ...    ${endline}=${\n}    ${iterations}=${EMPTY}    ${iter_start}=1    ${iter_j_offset}=0
369     [Documentation]    Resolve expected text from template, provided response shuld be equal.
370     ...    If \${normalize_json}, perform normalization before comparison.
371     # TODO: Support for XML-aware comparison could be added, but there are issues with namespaces and similar.
372     ${expected_text} =    Resolve_Text_From_Template_Folder    folder=${folder}    base_name=${base_name}    extension=${extension}    mapping=${mapping}    endline=${endline}
373     ...    iterations=${iterations}    iter_start=${iter_start}    iter_j_offset=${iter_j_offset}
374     BuiltIn.Run_Keyword_And_Return_If    """${expected_text}""" == """${EMPTY}"""    BuiltIn.Should_Be_Equal    ${EMPTY}    ${response}
375     BuiltIn.Run_Keyword_If    ${normalize_json}    Normalize_Jsons_And_Compare    expected_raw=${expected_text}    actual_raw=${response}
376     ...    ELSE    BuiltIn.Should_Be_Equal    ${expected_text}    ${response}
377
378 Get_From_Uri
379     [Arguments]    ${uri}    ${accept}=${ACCEPT_EMPTY}    ${session}=default    ${normalize_json}=False    ${jmes_path}=${EMPTY}    ${http_timeout}=${EMPTY}
380     ...    ${keys_with_volatiles}=${EMPTY}    ${log_response}=True
381     [Documentation]    GET data from given URI, check status code and return response text.
382     ...    \${accept} is a Python object with headers to use.
383     ...    If \${normalize_json}, normalize as JSON text before returning.
384     BuiltIn.Log    ${uri}
385     BuiltIn.Log    ${accept}
386     ${response} =    BuiltIn.Run_Keyword_If    """${http_timeout}""" == """${EMPTY}"""    RequestsLibrary.Get_Request    alias=${session}    uri=${uri}    headers=${accept}
387     ...    ELSE    RequestsLibrary.Get_Request    alias=${session}    uri=${uri}    headers=${accept}    timeout=${http_timeout}
388     Check_Status_Code    ${response}    log_response=${log_response}
389     BuiltIn.Run_Keyword_Unless    ${normalize_json}    BuiltIn.Return_From_Keyword    ${response.text}
390     ${text_normalized} =    norm_json.normalize_json_text    ${response.text}    jmes_path=${jmes_path}    keys_with_volatiles=${keys_with_volatiles}
391     [Return]    ${text_normalized}
392
393 Put_To_Uri
394     [Arguments]    ${uri}    ${data}    ${content_type}    ${accept}    ${session}=default    ${normalize_json}=False
395     ...    ${jmes_path}=${EMPTY}    ${http_timeout}=${EMPTY}
396     [Documentation]    PUT data to given URI, check status code and return response text.
397     ...    \${content_type} and \${accept} are mandatory Python objects with headers to use.
398     ...    If \${normalize_json}, normalize text before returning.
399     BuiltIn.Log    ${uri}
400     BuiltIn.Log    ${data}
401     BuiltIn.Log    ${content_type}
402     BuiltIn.Log    ${accept}
403     ${headers} =    Join_Two_Headers    first=${content_type}    second=${accept}
404     ${response} =    BuiltIn.Run_Keyword_If    """${http_timeout}""" == """${EMPTY}"""    RequestsLibrary.Put_Request    alias=${session}    uri=${uri}    data=${data}
405     ...    headers=${headers}
406     ...    ELSE    RequestsLibrary.Put_Request    alias=${session}    uri=${uri}    data=${data}    headers=${headers}
407     ...    timeout=${http_timeout}
408     Check_Status_Code    ${response}
409     BuiltIn.Run_Keyword_Unless    ${normalize_json}    BuiltIn.Return_From_Keyword    ${response.text}
410     ${text_normalized} =    norm_json.normalize_json_text    ${response.text}    jmes_path=${jmes_path}
411     [Return]    ${text_normalized}
412
413 Post_To_Uri
414     [Arguments]    ${uri}    ${data}    ${content_type}    ${accept}    ${session}=default    ${normalize_json}=False
415     ...    ${jmes_path}=${EMPTY}    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${explicit_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}
416     [Documentation]    POST data to given URI, check status code and return response text.
417     ...    \${content_type} and \${accept} are mandatory Python objects with headers to use.
418     ...    If \${normalize_json}, normalize text before returning.
419     BuiltIn.Log    ${uri}
420     BuiltIn.Log    ${data}
421     BuiltIn.Log    ${content_type}
422     BuiltIn.Log    ${accept}
423     ${headers} =    Join_Two_Headers    first=${content_type}    second=${accept}
424     ${response} =    BuiltIn.Run_Keyword_If    """${http_timeout}""" == """${EMPTY}"""    RequestsLibrary.Post_Request    alias=${session}    uri=${uri}    data=${data}
425     ...    headers=${headers}
426     ...    ELSE    RequestsLibrary.Post_Request    alias=${session}    uri=${uri}    data=${data}    headers=${headers}
427     ...    timeout=${http_timeout}
428     Check_Status_Code    ${response}    additional_allowed_status_codes=${additional_allowed_status_codes}    explicit_status_codes=${explicit_status_codes}
429     BuiltIn.Run_Keyword_Unless    ${normalize_json}    BuiltIn.Return_From_Keyword    ${response.text}
430     ${text_normalized} =    norm_json.normalize_json_text    ${response.text}    jmes_path=${jmes_path}
431     [Return]    ${text_normalized}
432
433 Check_Status_Code
434     [Arguments]    ${response}    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${explicit_status_codes}=${NO_STATUS_CODES}    ${log_response}=True
435     [Documentation]    Log response text, check status_code is one of allowed ones. In cases where this keyword is
436     ...    called in a WUKS it could end up logging tons of data and it may be desired to skip the logging by passing
437     ...    log_response=False, but by default it remains True.
438     # TODO: Remove overlap with keywords from Utils.robot
439     Run Keyword If    "${log_response}" == "True"    BuiltIn.Log    ${response.text}
440     Run Keyword If    "${log_response}" == "True"    BuiltIn.Log    ${response.status_code}
441     # In order to allow other existing keywords to consume this keyword by passing a single non-list status code, we need to
442     # check the type of the argument passed and convert those single non-list codes in to a one item list
443     ${status_codes_type} =    Evaluate    type($additional_allowed_status_codes).__name__
444     ${allowed_status_codes_list} =    Run Keyword If    "${status_codes_type}"!="list"    Create List    ${additional_allowed_status_codes}
445     ...    ELSE    Set Variable    ${additional_allowed_status_codes}
446     ${status_codes_type} =    Evaluate    type($explicit_status_codes).__name__
447     ${explicit_status_codes_list} =    Run Keyword If    "${status_codes_type}"!="list"    Create List    ${explicit_status_codes}
448     ...    ELSE    Set Variable    ${explicit_status_codes}
449     BuiltIn.Run_Keyword_And_Return_If    """${explicit_status_codes_list}""" != """${NO_STATUS_CODES}"""    Collections.List_Should_Contain_Value    ${explicit_status_codes_list}    ${response.status_code}
450     ${final_allowd_list} =    Collections.Combine_Lists    ${ALLOWED_STATUS_CODES}    ${allowed_status_codes_list}
451     Collections.List_Should_Contain_Value    ${final_allowd_list}    ${response.status_code}
452
453 Join_Two_Headers
454     [Arguments]    ${first}    ${second}
455     [Documentation]    Take two dicts, join them, return result. Second argument values take precedence.
456     ${accumulator} =    Collections.Copy_Dictionary    ${first}
457     ${items_to_add} =    Collections.Get_Dictionary_Items    ${second}
458     Collections.Set_To_Dictionary    ${accumulator}    @{items_to_add}
459     BuiltIn.Log    ${accumulator}
460     [Return]    ${accumulator}
461
462 Resolve_Text_From_Template_Folder
463     [Arguments]    ${folder}    ${name_prefix}=${EMPTY}    ${base_name}=data    ${extension}=json    ${mapping}={}    ${iterations}=${EMPTY}
464     ...    ${iter_start}=1    ${iter_j_offset}=0    ${endline}=${\n}
465     [Documentation]    Read a template from folder, strip endline, make changes according to mapping, return the result.
466     ...    If \${iterations} value is present, put text together from "prolog", "item" and "epilog" parts,
467     ...    where additional template variable ${i} goes from ${iter_start}, by one ${iterations} times.
468     ...    Template variable ${j} is calculated as ${i} incremented by offset ${iter_j_offset} ( j = i + iter_j_offset )
469     ...    used to create non uniform data in order to be able to validate UPDATE operations.
470     ...    POST (as opposed to PUT) needs slightly different data, \${name_prefix} may be used to distinguish.
471     ...    (Actually, it is GET who formats data differently when URI is a top-level container.)
472     BuiltIn.Run_Keyword_And_Return_If    not "${iterations}"    Resolve_Text_From_Template_File    folder=${folder}    file_name=${name_prefix}${base_name}.${extension}    mapping=${mapping}
473     ${prolog} =    Resolve_Text_From_Template_File    folder=${folder}    file_name=${name_prefix}${base_name}.prolog.${extension}    mapping=${mapping}
474     ${epilog} =    Resolve_Text_From_Template_File    folder=${folder}    file_name=${name_prefix}${base_name}.epilog.${extension}    mapping=${mapping}
475     # Even POST uses the same item template (except indentation), so name prefix is ignored.
476     ${item_template} =    Resolve_Text_From_Template_File    folder=${folder}    file_name=${base_name}.item.${extension}    mapping=${mapping}
477     ${items} =    BuiltIn.Create_List
478     ${separator} =    BuiltIn.Set_Variable_If    '${extension}' != 'json'    ${endline}    ,${endline}
479     FOR    ${iteration}    IN RANGE    ${iter_start}    ${iterations}+${iter_start}
480         BuiltIn.Run_Keyword_If    ${iteration} > ${iter_start}    Collections.Append_To_List    ${items}    ${separator}
481         ${j} =    BuiltIn.Evaluate    ${iteration}+${iter_j_offset}
482         ${item} =    BuiltIn.Evaluate    string.Template('''${item_template}''').substitute({"i":"${iteration}", "j":${j}})    modules=string
483         Collections.Append_To_List    ${items}    ${item}
484         # TODO: The following makes ugly result for iterations=0. Should we fix that?
485     END
486     ${final_text} =    BuiltIn.Catenate    SEPARATOR=    ${prolog}    ${endline}    @{items}    ${endline}
487     ...    ${epilog}
488     [Return]    ${final_text}
489
490 Resolve_Text_From_Template_File
491     [Arguments]    ${folder}    ${file_name}    ${mapping}={}
492     [Documentation]    Check if ${folder}.${ODL_STREAM}/${file_name} exists. If yes read and Log contents of file ${folder}.${ODL_STREAM}/${file_name},
493     ...    remove endline, perform safe substitution, return result.
494     ...    If no do it with the default ${folder}/${file_name}.
495     ${file_path_stream}=    BuiltIn.Set Variable    ${folder}.${ODL_STREAM}${/}${file_name}
496     ${file_stream_exists}=    BuiltIn.Run Keyword And Return Status    OperatingSystem.File Should Exist    ${file_path_stream}
497     ${file_path}=    BuiltIn.Set Variable If    ${file_stream_exists}    ${file_path_stream}    ${folder}${/}${file_name}
498     ${template} =    OperatingSystem.Get_File    ${file_path}
499     BuiltIn.Log    ${template}
500     ${final_text} =    BuiltIn.Evaluate    string.Template('''${template}'''.rstrip()).safe_substitute(${mapping})    modules=string
501     # Final text is logged where used.
502     [Return]    ${final_text}
503
504 Normalize_Jsons_And_Compare
505     [Arguments]    ${expected_raw}    ${actual_raw}
506     [Documentation]    Use norm_json to normalize both JSON arguments, call Should_Be_Equal.
507     ${expected_normalized} =    norm_json.normalize_json_text    ${expected_raw}
508     ${actual_normalized} =    norm_json.normalize_json_text    ${actual_raw}
509     # Should_Be_Equal shall print nice diff-style line comparison.
510     BuiltIn.Should_Be_Equal    ${expected_normalized}    ${actual_normalized}
511     # TODO: Add garbage collection? Check whether the temporary data accumulates.
512
513 Normalize_Jsons_With_Bits_And_Compare
514     [Arguments]    ${expected_raw}    ${actual_raw}    ${keys_with_bits}=${KEYS_WITH_BITS}
515     [Documentation]    Use norm_json to normalize both JSON arguments, call Should_Be_Equal.
516     ${expected_normalized} =    norm_json.normalize_json_text    ${expected_raw}    keys_with_bits=${keys_with_bits}
517     ${actual_normalized} =    norm_json.normalize_json_text    ${actual_raw}    keys_with_bits=${keys_with_bits}
518     BuiltIn.Should_Be_Equal    ${expected_normalized}    ${actual_normalized}