Add TemplatedRequests.robot Resource 87/33987/62
authorVratko Polak <vrpolak@cisco.com>
Thu, 18 Feb 2016 11:04:46 +0000 (12:04 +0100)
committerGerrit Code Review <gerrit@opendaylight.org>
Fri, 19 Feb 2016 00:04:13 +0000 (00:04 +0000)
+ Resource to simplify handling Restconf requests using data from files.
+ Suite with basic 1-node car/people tests, for library verification.
+ Data for the suite, not suitable for more thorough car/people tests.
+ The suite added to test-libraries testplan.
+ Add mising Utils dependency to SetupUtils.

Change-Id: I851376426132669b9d344a19118cfe06b38d6b97
Signed-off-by: Vratko Polak <vrpolak@cisco.com>
29 files changed:
csit/libraries/HsfJson/hsf_json.py
csit/libraries/SetupUtils.robot
csit/libraries/TemplatedRequests.robot [new file with mode: 0644]
csit/libraries/Utils.robot
csit/libraries/norm_json.py [new file with mode: 0644]
csit/suites/controller/OneNode_Datastore/carpeople_library_test.robot [new file with mode: 0644]
csit/testplans/test-libraries.txt
csit/variables/Variables.py
csit/variables/carpeople/libtest/car/data.json [new file with mode: 0644]
csit/variables/carpeople/libtest/car/location.uri [new file with mode: 0644]
csit/variables/carpeople/libtest/car_person/data.json [new file with mode: 0644]
csit/variables/carpeople/libtest/car_person/location.uri [new file with mode: 0644]
csit/variables/carpeople/libtest/cars/data.epilog.json [new file with mode: 0644]
csit/variables/carpeople/libtest/cars/data.item.json [new file with mode: 0644]
csit/variables/carpeople/libtest/cars/data.prolog.json [new file with mode: 0644]
csit/variables/carpeople/libtest/cars/location.uri [new file with mode: 0644]
csit/variables/carpeople/libtest/cars/post_data.epilog.json [new file with mode: 0644]
csit/variables/carpeople/libtest/cars/post_data.prolog.json [new file with mode: 0644]
csit/variables/carpeople/libtest/cars_people/location.uri [new file with mode: 0644]
csit/variables/carpeople/libtest/people/data.epilog.json [new file with mode: 0644]
csit/variables/carpeople/libtest/people/data.item.json [new file with mode: 0644]
csit/variables/carpeople/libtest/people/data.prolog.json [new file with mode: 0644]
csit/variables/carpeople/libtest/people/location.uri [new file with mode: 0644]
csit/variables/carpeople/libtest/people/post_data.epilog.json [new file with mode: 0644]
csit/variables/carpeople/libtest/people/post_data.prolog.json [new file with mode: 0644]
csit/variables/carpeople/libtest/person/data.json [new file with mode: 0644]
csit/variables/carpeople/libtest/person/location.uri [new file with mode: 0644]
csit/variables/carpeople/libtest/purchase/location.uri [new file with mode: 0644]
csit/variables/carpeople/libtest/purchase/post_data.json [new file with mode: 0644]

index d478ff0e295d8acd6dd029068e70996233730688..f581e66c3d216cf6a532b29a6afb7d2c625a543e 100644 (file)
@@ -4,6 +4,9 @@
 # This program and the accompanying materials are made available under the
 # terms of the Eclipse Public License v1.0 which accompanies this distribution,
 # and is available at http://www.eclipse.org/legal/epl-v10.html
+#
+# FIXME: Use ${CURDIR}/../norm_json.py everywhere, then delete ${CURDIR}.
+# https://trello.com/c/N3s0xhc6/260-rename-hfsjson-hsf-json-py-to-something-friendlier
 
 try:
     import simplejson as _json
index 573677787e45ec0fd177ba960e30d33d11a5c113..1f865a28c5f5ee5035395e2d1d2f5c077735830c 100644 (file)
@@ -4,6 +4,7 @@ Documentation     Simple resource with setup keywords which combine FailFast and
 ...               See FailFast.robot documentation for intricacies of that library.
 Resource          ${CURDIR}/FailFast.robot
 Resource          ${CURDIR}/KarafKeywords.robot
+Resource          ${CURDIR}/Utils.robot
 
 *** Keywords ***
 Setup_Utils_For_Setup_And_Teardown
diff --git a/csit/libraries/TemplatedRequests.robot b/csit/libraries/TemplatedRequests.robot
new file mode 100644 (file)
index 0000000..9a26574
--- /dev/null
@@ -0,0 +1,396 @@
+*** Settings ***
+Documentation     Resource for supporting http Requests based on data stored in files.
+...
+...               Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+...
+...               This program and the accompanying materials are made available under the
+...               terms of the Eclipse Public License v1.0 which accompanies this distribution,
+...               and is available at http://www.eclipse.org/legal/epl-v10.html
+...
+...
+...               The main strength of this library are *_As_*_Templated keywords
+...               User gives a path to directory where files with templates for URI
+...               and XML (or JSON) data are present, and a mapping with substitution to make;
+...               the keywords will take it from there.
+...               Mapping can be given as a dict object, or as its json text representation.
+...               Simple example (tidy insists on single space where 4 spaces should be):
+...               TemplatedRequests.Put_As_Json_Templated folder=${VAR_BASE}/person mapping={"NAME":"joe"}
+...               TemplatedRequests.Get_As_Json_Templated folder=${VAR_BASE}/person mapping={"NAME":"joe"} verify=True
+...
+...               In that example, we are PUTting "person" data with specified value for "NAME" placeholder.
+...               We are not verifying PUT response (probably empty string which is not a valid JSON),
+...               but we are issuing GET (same URI) and verifying the repsonse matches the same data.
+...               Both lines are returning text response, but in the example we are not saving it into variable.
+...
+...               Optionally, *_As_*_Templated keywords call verification of response.
+...               There are separate Verify_* keywords, for users who use intermediate processing.
+...               For JSON responses, there is a support for normalizing.
+...               *_Templated keywords without As allow more customization at cost of more arguments.
+...               *_Uri keywords do not use templates, but may be useful in general,
+...               perhaps for users who call Resolve_Text_* keywords.
+...               *_As_*_Uri are the less flexible but less argument-heavy versions of *_Uri keywords.
+...
+...               This resource supports generating data with simple lists.
+...               ${iterations} argument control number of items, "$i" will be substituted
+...               automatically (not by the provided mapping) with integers starting with ${iter_start} (default 1).
+...               For example "iterations=2 iter_start=3" will create items with i=3 and i=4.
+...
+...               This implementation relies on file names to distinguish data.
+...               Each file is expected to end in newline, compiled data has final newline removed.
+...               Here is a table so that users can create their own templates:
+...               location.uri: Template with URI.
+...               data.xml: Template with XML data to send, or GET data to expect.
+...               data.json: Template with JSON data to send, or GET data to expect.
+...               post_data.xml: Template with XML data to POST, (different from GET response).
+...               post_data.json: Template with JSON data to POST, (different from GET response).
+...               response.xml: Template with PUT or POST XML response to expect.
+...               response.json: Template with PUT or POST JSON response to expect.
+...               *.prolog.*: Temlate with data before iterated items.
+...               *.item.*: Template with data piece corresponding to one item.
+...               *.epilog.*: Temlate with data after iterated items.
+...
+...               One typical use of this Resource is to make runtime changes to ODL configuration.
+...               Different ODL parts have varying ways of configuration,
+...               this library affects only the Config Subsystem way.
+...               Config Subsystem has (except for Java APIs mostly available only from inside of ODL)
+...               a NETCONF server as its publicly available entry point.
+...               Netconf-connector feature makes this netconf server available for RESTCONF calls.
+...               Be careful to use appropriate feature, odl-netconf-connector* does not work in cluster.
+...
+...               This Resource currently calls RequestsLibrary directly,
+...               so it does not work with AuthStandalone or similar.
+...               This Resource does not maintain any internal Sessions.
+...               If caller does not provide any, session with alias "default" is used.
+...               There is a helper Keyword to create the "default" session.
+...               The session used is assumed to have most things pre-configured appropriately,
+...               which includes auth, host, port and (lack of) base URI.
+...               It is recommended to have everything past port (for example /restconf) be defined
+...               not in the session, but in URI data of individual templates.
+...               Headers are set in Keywords appropriately.
+...
+...               These Keywords contain frequent BuiltIn.Log invocations,
+...               so they are not suited for scale or performance suites.
+...               And as usual, performance tests should use specialized utilities,
+...               as Robot in general and this Resource specifically will be too slow.
+...
+...               As this Resource makes assumptions about intended headers,
+...               it is not flexible enough for suites specifically testing Restconf corner cases.
+...               Also, list of allowed http status codes is quite rigid and broad.
+...
+...               Rules for ordering Keywords within this Resource:
+...               1. User friendlier Keywords first.
+...               2. Get, Put, Post, Delete, Verify.
+...               3. Within class of equally usable, use order in which a suite would call them.
+...               4. Higher-level Keywords first.
+...               5. Json before Xml.
+...               Motivation: Users read from the start, so it is important
+...               to offer them the better-to-use Keywords first.
+...               https://wiki.opendaylight.org/view/Integration/Test/Test_Code_Guidelines#Keyword_ordering
+...               In this case, templates are nicer that raw data,
+...               *_As_* keywords are better than messing wth explicit header dicts,
+...               Json is less prone to element ordering issues.
+...               PUT does not fail on existing element, also it does not allow
+...               shortened URIs (container instead keyed list element) as Post does.
+...
+...               TODO: Add ability to override allowed status codes,
+...               so that negative tests do not need to parse the failure message.
+...
+...               TODO: Migrate suites to this Resource and remove *ViaRestconf Resources.
+...
+...               TODO: Currently the verification step is only in *_As_*_Templated keywords.
+...               It could be moved to "non-as" *_Templated ones,
+...               but that would take even more horizontal space. Is it worth doing?
+...
+...               TODO: Should iterations=0 be supported for JSON (remove [])?
+...
+...               TODO: Currently, ${ACCEPT_EMPTY} is used for JSON-expecting requests.
+...               perhaps explicit ${ACCEPT_JSON} will be better, even if it sends few bytes more?
+Library           Collections
+Library           OperatingSystem
+Library           RequestsLibrary
+Library           ${CURDIR}/norm_json.py
+Variables         ${CURDIR}/../variables/Variables.py
+
+*** Variables ***
+# TODO: Make the following list more narrow when Bug 2594 is fixed.
+@{ALLOWED_STATUS_CODES}    ${200}    ${201}    ${204}    # List of integers, not strings. Used by both PUT and DELETE.
+
+*** Keywords ***
+Create_Default_Session
+    [Arguments]    ${url}=http://${ODL_SYSTEM_IP}:${RESTCONFPORT}    ${auth}=${AUTH}
+    [Documentation]    Create "default" session to ${url} with default authentication.
+    ...    This Keyword is in this Resource only so that user do not need to call RequestsLibrary directly.
+    RequestsLibrary.Create_Session    alias=default    url=${url}    auth=${auth}
+
+Get_As_Json_Templated
+    [Arguments]    ${folder}    ${mapping}={}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
+    [Documentation]    Add arguments sensible for JSON data, return Get_Templated response text.
+    ...    Optionally, verification against JSON data (may be iterated) is called.
+    ${response_text} =    Get_Templated    folder=${folder}    mapping=${mapping}    accept=${ACCEPT_EMPTY}    session=${session}    normalize_json=True
+    BuiltIn.Run_Keyword_If    ${verify}    Verify_Response_As_Json_Templated    response=${response_text}    folder=${folder}    base_name=data    mapping=${mapping}
+    ...    iterations=${iterations}    iter_start=${iter_start}
+    [Return]    ${response_text}
+
+Get_As_Xml_Templated
+    [Arguments]    ${folder}    ${mapping}={}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
+    [Documentation]    Add arguments sensible for XML data, return Get_Templated response text.
+    ...    Optionally, verification against XML data (may be iterated) is called.
+    ${response_text} =    Get_Templated    folder=${folder}    mapping=${mapping}    accept=${ACCEPT_XML}    session=${session}    normalize_json=False
+    BuiltIn.Run_Keyword_If    ${verify}    Verify_Response_As_Xml_Templated    response=${response_text}    folder=${folder}    base_name=data    mapping=${mapping}
+    ...    iterations=${iterations}    iter_start=${iter_start}
+    [Return]    ${response_text}
+
+Put_As_Json_Templated
+    [Arguments]    ${folder}    ${mapping}={}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
+    [Documentation]    Add arguments sensible for JSON data, return Put_Templated response text.
+    ...    Optionally, verification against response.json (no iteration) is called.
+    ${response_text} =    Put_Templated    folder=${folder}    base_name=data    extension=json    accept=${ACCEPT_EMPTY}    content_type=${HEADERS_YANG_JSON}
+    ...    mapping=${mapping}    session=${session}    normalize_json=True    endline=${\n}    iterations=${iterations}    iter_start=${iter_start}
+    BuiltIn.Run_Keyword_If    ${verify}    Verify_Response_As_Json_Templated    response=${response_text}    folder=${folder}    base_name=response    mapping=${mapping}
+    [Return]    ${response_text}
+
+Put_As_Xml_Templated
+    [Arguments]    ${folder}    ${mapping}={}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
+    [Documentation]    Add arguments sensible for XML data, return Put_Templated response text.
+    ...    Optionally, verification against response.xml (no iteration) is called.
+    # In case of iterations, we use endlines in data to send, as it should not matter and it is more readable.
+    ${response_text} =    Put_Templated    folder=${folder}    base_name=data    extension=xml    accept=${ACCEPT_XML}    content_type=${HEADERS_XML}
+    ...    mapping=${mapping}    session=${session}    normalize_json=False    endline=${\n}    iterations=${iterations}    iter_start=${iter_start}
+    BuiltIn.Run_Keyword_If    ${verify}    Verify_Response_As_Xml_Templated    response=${response_text}    folder=${folder}    base_name=response    mapping=${mapping}
+    [Return]    ${response_text}
+
+Post_As_Json_Templated
+    [Arguments]    ${folder}    ${mapping}={}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
+    [Documentation]    Add arguments sensible for JSON data, return Post_Templated response text.
+    ...    Optionally, verification against response.json (no iteration) is called.
+    ${response_text} =    Post_Templated    folder=${folder}    base_name=data    extension=json    accept=${ACCEPT_EMPTY}    content_type=${HEADERS_YANG_JSON}
+    ...    mapping=${mapping}    session=${session}    normalize_json=True    endline=${\n}    iterations=${iterations}    iter_start=${iter_start}
+    BuiltIn.Run_Keyword_If    ${verify}    Verify_Response_As_Json_Templated    response=${response_text}    folder=${folder}    base_name=response    mapping=${mapping}
+    [Return]    ${response_text}
+
+Post_As_Xml_Templated
+    [Arguments]    ${folder}    ${mapping}={}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
+    [Documentation]    Add arguments sensible for XML data, return Post_Templated response text.
+    ...    Optionally, verification against response.xml (no iteration) is called.
+    # In case of iterations, we use endlines in data to send, as it should not matter and it is more readable.
+    ${response_text} =    Post_Templated    folder=${folder}    base_name=data    extension=xml    accept=${ACCEPT_XML}    content_type=${HEADERS_XML}
+    ...    mapping=${mapping}    session=${session}    normalize_json=False    endline=${\n}    iterations=${iterations}    iter_start=${iter_start}
+    BuiltIn.Run_Keyword_If    ${verify}    Verify_Response_As_Xml_Templated    response=${response_text}    folder=${folder}    base_name=response    mapping=${mapping}
+    [Return]    ${response_text}
+
+Delete_Templated
+    [Arguments]    ${folder}    ${mapping}={}    ${session}=default
+    [Documentation]    Resolve URI from folder, issue DELETE request.
+    ${uri} =    Resolve_Text_From_Template_Folder    folder=${folder}    base_name=location    extension=uri    mapping=${mapping}
+    ${response_text} =    Delete_From_Uri    uri=${uri}    session=${session}
+    [Return]    ${response_text}
+
+Verify_Response_As_Json_Templated
+    [Arguments]    ${response}    ${folder}    ${base_name}=response    ${mapping}={}    ${iterations}=${EMPTY}    ${iter_start}=1
+    [Documentation]    Resolve expected JSON data, should be equal to provided \${response}.
+    ...    JSON normalization is used, endlines enabled for readability.
+    Verify_Response_Templated    response=${response}    folder=${folder}    base_name=${base_name}    extension=json    mapping=${mapping}    normalize_json=True
+    ...    endline=${\n}    iterations=${iterations}    iter_start=${iter_start}
+
+Verify_Response_As_Xml_Templated
+    [Arguments]    ${response}    ${folder}    ${base_name}=response    ${mapping}={}    ${iterations}=${EMPTY}    ${iter_start}=1
+    [Documentation]    Resolve expected XML data, should be equal to provided \${response}.
+    ...    Endline set to empty, as this Resource does not support indented XML comparison.
+    Verify_Response_Templated    response=${response}    folder=${folder}    base_name=${base_name}    extension=xml    mapping=${mapping}    normalize_json=False
+    ...    endline=${EMPTY}    iterations=${iterations}    iter_start=${iter_start}
+
+Get_As_Json_From_Uri
+    [Arguments]    ${uri}    ${session}=default
+    [Documentation]    Specify JSON headers and return Get_From_Uri normalized response text.
+    ${response_text} =    Get_From_Uri    uri=${uri}    accept=${ACCEPT_EMPTY}    session=${session}    normalize_json=True
+    [Return]    ${response_text}
+
+Get_As_Xml_From_Uri
+    [Arguments]    ${uri}    ${session}=default
+    [Documentation]    Specify XML headers and return Get_From_Uri response text.
+    ${response_text} =    Get_From_Uri    uri=${uri}    accept=${ACCEPT_XML}    session=${session}    normalize_json=False
+    [Return]    ${response_text}
+
+Put_As_Json_To_Uri
+    [Arguments]    ${uri}    ${data}    ${session}=default
+    [Documentation]    Specify JSON headers and return Put_To_Uri normalized response text.
+    ...    Yang json content type is used as a workaround to RequestsLibrary json conversion eagerness.
+    ${response_text} =    Put_To_Uri    uri=${uri}    data=${data}    accept=${ACCEPT_EMPTY}    content_type=${HEADERS_YANG_JSON}    session=${session}
+    ...    normalize_json=True
+    [Return]    ${response_text}
+
+Put_As_Xml_To_Uri
+    [Arguments]    ${uri}    ${data}    ${session}=default
+    [Documentation]    Specify XML headers and return Put_To_Uri response text.
+    ${response_text} =    Put_To_Uri    uri=${uri}    data=${data}    accept=${ACCEPT_XML}    content_type=${HEADERS_XML}    session=${session}
+    ...    normalize_json=False
+    [Return]    ${response_text}
+
+Post_As_Json_To_Uri
+    [Arguments]    ${uri}    ${data}    ${session}=default
+    [Documentation]    Specify JSON headers and return Post_To_Uri normalized response text.
+    ...    Yang json content type is used as a workaround to RequestsLibrary json conversion eagerness.
+    ${response_text} =    Post_To_Uri    uri=${uri}    data=${data}    accept=${ACCEPT_EMPTY}    content_type=${HEADERS_YANG_JSON}    session=${session}
+    ...    normalize_json=True
+    [Return]    ${response_text}
+
+Post_As_Xml_To_Uri
+    [Arguments]    ${uri}    ${data}    ${session}=default
+    [Documentation]    Specify XML headers and return Post_To_Uri response text.
+    ${response_text} =    Post_To_Uri    uri=${uri}    data=${data}    accept=${ACCEPT_XML}    content_type=${HEADERS_XML}    session=${session}
+    ...    normalize_json=False
+    [Return]    ${response_text}
+
+Delete_From_Uri
+    [Arguments]    ${uri}    ${session}=default
+    [Documentation]    DELETE resource at URI, check status_code and return response text..
+    BuiltIn.Log    ${uri}
+    ${response} =    RequestsLibrary.Delete_Request    alias=${session}    uri=${uri}
+    Check_Status_Code    ${response}
+    [Return]    ${response.text}
+
+Get_Templated
+    [Arguments]    ${folder}    ${accept}    ${mapping}={}    ${session}=default    ${normalize_json}=False
+    [Documentation]    Resolve URI from folder, call Get_From_Uri, return response text.
+    ${uri} =    Resolve_Text_From_Template_Folder    folder=${folder}    base_name=location    extension=uri    mapping=${mapping}
+    ${response_text} =    Get_From_Uri    uri=${uri}    accept=${accept}    session=${session}    normalize_json=${normalize_json}
+    [Return]    ${response_text}
+
+Put_Templated
+    [Arguments]    ${folder}    ${base_name}    ${extension}    ${content_type}    ${accept}    ${mapping}={}
+    ...    ${session}=default    ${normalize_json}=False    ${endline}=${\n}    ${iterations}=${EMPTY}    ${iter_start}=1
+    [Documentation]    Resolve URI and data from folder, call Put_To_Uri, return response text.
+    ${uri} =    Resolve_Text_From_Template_Folder    folder=${folder}    base_name=location    extension=uri    mapping=${mapping}
+    ${data} =    Resolve_Text_From_Template_Folder    folder=${folder}    base_name=${base_name}    extension=${extension}    mapping=${mapping}    endline=${endline}
+    ...    iterations=${iterations}    iter_start=${iter_start}
+    ${response_text} =    Put_To_Uri    uri=${uri}    data=${data}    content_type=${content_type}    accept=${accept}    session=${session}
+    ...    normalize_json=${normalize_json}
+    [Return]    ${response_text}
+
+Post_Templated
+    [Arguments]    ${folder}    ${base_name}    ${extension}    ${content_type}    ${accept}    ${mapping}={}
+    ...    ${session}=default    ${normalize_json}=False    ${endline}=${\n}    ${iterations}=${EMPTY}    ${iter_start}=1
+    [Documentation]    Resolve URI and data from folder, call Post_To_Uri, return response text.
+    ${uri} =    Resolve_Text_From_Template_Folder    folder=${folder}    base_name=location    extension=uri    mapping=${mapping}
+    ${data} =    Resolve_Text_From_Template_Folder    folder=${folder}    name_prefix=post_    base_name=${base_name}    extension=${extension}    mapping=${mapping}
+    ...    endline=${endline}    iterations=${iterations}    iter_start=${iter_start}
+    ${response_text} =    Post_To_Uri    uri=${uri}    data=${data}    content_type=${content_type}    accept=${accept}    session=${session}
+    ...    normalize_json=${normalize_json}
+    [Return]    ${response_text}
+
+Verify_Response_Templated
+    [Arguments]    ${response}    ${folder}    ${base_name}    ${extension}    ${mapping}={}    ${normalize_json}=False
+    ...    ${endline}=${\n}    ${iterations}=${EMPTY}    ${iter_start}=1
+    [Documentation]    Resolve expected text from template, provided response shuld be equal.
+    ...    If \${normalize_json}, perform normalization before comparison.
+    # TODO: Support for XML-aware comparison could be added, but there are issues with namespaces and similar.
+    ${expected_text} =    Resolve_Text_From_Template_Folder    folder=${folder}    base_name=${base_name}    extension=${extension}    mapping=${mapping}    endline=${endline}
+    ...    iterations=${iterations}    iter_start=${iter_start}
+    BuiltIn.Run_Keyword_If    ${normalize_json}    Normalize_Jsons_And_Compare    ${expected_text}    ${response}
+    ...    ELSE    BuiltIn.Should_Be_Equal    ${expected_text}    ${response}
+
+Get_From_Uri
+    [Arguments]    ${uri}    ${accept}    ${session}=default    ${normalize_json}=False
+    [Documentation]    GET data from given URI, check status code and return response text.
+    ...    \${accept} is a mandatory Python object with headers to use.
+    ...    If \${normalize_json}, normalize text before returning.
+    BuiltIn.Log    ${uri}
+    BuiltIn.Log    ${accept}
+    ${response} =    RequestsLibrary.Get_Request    alias=${session}    uri=${uri}    headers=${accept}
+    Check_Status_Code    ${response}
+    BuiltIn.Run_Keyword_Unless    ${normalize_json}    BuiltIn.Return_From_Keyword    ${response.text}
+    ${text_normalized} =    norm_json.normalize_json_text    ${response.text}
+    [Return]    ${text_normalized}
+
+Put_To_Uri
+    [Arguments]    ${uri}    ${data}    ${content_type}    ${accept}    ${session}=default    ${normalize_json}=False
+    [Documentation]    PUT data to given URI, check status code and return response text.
+    ...    \${content_type} and \${accept} are mandatory Python objects with headers to use.
+    ...    If \${normalize_json}, normalize text before returning.
+    BuiltIn.Log    ${uri}
+    BuiltIn.Log    ${data}
+    BuiltIn.Log    ${content_type}
+    BuiltIn.Log    ${accept}
+    ${headers} =    Join_Two_Headers    first=${content_type}    second=${accept}
+    ${response} =    RequestsLibrary.Put_Request    alias=${session}    uri=${uri}    data=${data}    headers=${headers}
+    Check_Status_Code    ${response}
+    BuiltIn.Run_Keyword_Unless    ${normalize_json}    BuiltIn.Return_From_Keyword    ${response.text}
+    ${text_normalized} =    norm_json.normalize_json_text    ${response.text}
+    [Return]    ${text_normalized}
+
+Post_To_Uri
+    [Arguments]    ${uri}    ${data}    ${content_type}    ${accept}    ${session}=default    ${normalize_json}=False
+    [Documentation]    POST data to given URI, check status code and return response text.
+    ...    \${content_type} and \${accept} are mandatory Python objects with headers to use.
+    ...    If \${normalize_json}, normalize text before returning.
+    BuiltIn.Log    ${uri}
+    BuiltIn.Log    ${data}
+    BuiltIn.Log    ${content_type}
+    BuiltIn.Log    ${accept}
+    ${headers} =    Join_Two_Headers    first=${content_type}    second=${accept}
+    ${response} =    RequestsLibrary.Post_Request    alias=${session}    uri=${uri}    data=${data}    headers=${headers}
+    Check_Status_Code    ${response}
+    BuiltIn.Run_Keyword_Unless    ${normalize_json}    BuiltIn.Return_From_Keyword    ${response.text}
+    ${text_normalized} =    norm_json.normalize_json_text    ${response.text}
+    [Return]    ${text_normalized}
+
+Check_Status_Code
+    [Arguments]    ${response}
+    [Documentation]    Log response text, check status_code is one of allowed ones.
+    # TODO: Remove overlap with keywords from Utils.robot
+    BuiltIn.Log    ${response.text}
+    BuiltIn.Log    ${response.status_code}
+    BuiltIn.Should_Contain    ${ALLOWED_STATUS_CODES}    ${response.status_code}
+    # TODO: Add support for 404 on Delete?
+
+Join_Two_Headers
+    [Arguments]    ${first}    ${second}
+    [Documentation]    Take two dicts, join them, return result. Second argument values take precedence.
+    ${accumulator} =    Collections.Copy_Dictionary    ${first}
+    ${items_to_add} =    Collections.Get_Dictionary_Items    ${second}
+    Collections.Set_To_Dictionary    ${accumulator}    @{items_to_add}
+    BuiltIn.Log    ${accumulator}
+    [Return]    ${accumulator}
+
+Resolve_Text_From_Template_Folder
+    [Arguments]    ${folder}    ${name_prefix}=${EMPTY}    ${base_name}=data    ${extension}=json    ${mapping}={}    ${iterations}=${EMPTY}
+    ...    ${iter_start}=1    ${endline}=${\n}
+    [Documentation]    Read a template from folder, strip endline, make changes according to mapping, return the result.
+    ...    If \${iterations} value is present, put text together from "prolog", "item" and "epilog" parts,
+    ...    where additional template variable ${i} goes from ${iter_start}, by one ${iterations} times.
+    ...    POST (as opposed to PUT) needs slightly different data, \${name_prefix} may be used to distinguish.
+    ...    (Actually, it is GET who formats data differently when URI is a top-level container.)
+    BuiltIn.Run_Keyword_And_Return_If    not "${iterations}"    Resolve_Text_From_Template_File    file_path=${folder}${/}${name_prefix}${base_name}.${extension}    mapping=${mapping}
+    ${prolog} =    Resolve_Text_From_Template_File    file_path=${folder}${/}${name_prefix}${base_name}.prolog.${extension}    mapping=${mapping}
+    ${epilog} =    Resolve_Text_From_Template_File    file_path=${folder}${/}${name_prefix}${base_name}.epilog.${extension}    mapping=${mapping}
+    # Even POST uses the same item template (except indentation), so name prefix is ignored.
+    ${item_template} =    Resolve_Text_From_Template_File    file_path=${folder}${/}${base_name}.item.${extension}    mapping=${mapping}
+    ${items} =    BuiltIn.Create_List
+    ${separator} =    BuiltIn.Set_Variable_If    '${extension}' != 'json'    ${endline}    ,${endline}
+    : FOR    ${iteration}    IN RANGE    ${iter_start}    ${iterations}+${iter_start}
+    \    # Add separator only if we are beyond first item.
+    \    BuiltIn.Run_Keyword_If    ${iteration} > 1    Collections.Append_To_List    ${items}    ${separator}
+    \    ${item} =    BuiltIn.Evaluate    string.Template('''${item_template}''').substitute({"i":"${iteration}"})    modules=string
+    \    Collections.Append_To_List    ${items}    ${item}
+    # TODO: The following makes ugly result for iterations=0. Should we fix that?
+    ${final_text} =    BuiltIn.Catenate    SEPARATOR=    ${prolog}    ${endline}    @{items}    ${endline}
+    ...    ${epilog}
+    [Return]    ${final_text}
+
+Resolve_Text_From_Template_File
+    [Arguments]    ${file_path}    ${mapping}={}
+    [Documentation]    Read an Log contents of file, remove endline, perform safe substitution, return result.
+    ${template} =    OperatingSystem.Get_File    ${file_path}
+    BuiltIn.Log    ${template}
+    ${final_text} =    BuiltIn.Evaluate    string.Template('''${template}'''.rstrip()).safe_substitute(${mapping})    modules=string
+    # Final text is logged where used.
+    [Return]    ${final_text}
+
+Normalize_Jsons_And_Compare
+    [Arguments]    ${actual_raw}    ${expected_raw}
+    [Documentation]    Use norm_json to normalize both JSON arguments, call Should_Be_Equal.
+    ${actual_normalized} =    norm_json.normalize_json_text    ${actual_raw}
+    ${expected_normalized} =    norm_json.normalize_json_text    ${expected_raw}
+    # Should_Be_Equal shall print nice diff-style line comparison.
+    BuiltIn.Should_Be_Equal    ${expected_normalized}    ${actual_normalized}
+    # TODO: Add garbage collection? Check whether the temporary data accumulates.
index 919da36a04d5850d5b6e44eac767741dbcc5ba0c..a18f2bec19ffec846ab62cb82c41a3364906b1d9 100644 (file)
@@ -87,7 +87,7 @@ Report_Failure_And_Point_To_Linked_Bugs
     ...    or as the first line of the test if FastFail module is not being
     ...    used. It reports the URL of the bug on console and also puts it
     ...    into the Robot log file.
-    ${test_skipped}=    BuiltIn.Evaluate    len(re.findall('SKIPPED', '''${TEST_MESSAGE}''')) > 0    modules=re
+    ${test_skipped}=    BuiltIn.Evaluate    len(re.findall('SKIPPED', """${TEST_MESSAGE}""")) > 0    modules=re
     BuiltIn.Return From Keyword If    ('${TEST_STATUS}' != 'FAIL') or ${test_skipped}
     ${newline}=    BuiltIn.Evaluate    chr(10)
     ${reference}=    String.Replace_String_Using_Regexp    ${SUITE_NAME}_${TEST_NAME}    [ /\.-]    _
diff --git a/csit/libraries/norm_json.py b/csit/libraries/norm_json.py
new file mode 100644 (file)
index 0000000..d46d6c7
--- /dev/null
@@ -0,0 +1,132 @@
+"""This module contains single a function for normalizing JSON strings."""
+# Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v1.0 which accompanies this distribution,
+# and is available at http://www.eclipse.org/legal/epl-v10.html
+
+import collections as _collections
+try:
+    import simplejson as _json
+except ImportError:  # Python2.7 calls it json.
+    import json as _json
+
+
+__author__ = "Vratko Polak"
+__copyright__ = "Copyright(c) 2015-2016, Cisco Systems, Inc."
+__license__ = "Eclipse Public License v1.0"
+__email__ = "vrpolak@cisco.com"
+
+
+# Internal details.
+
+
+class _Hsfl(list):
+    """
+    Hashable sorted frozen list implementation stub.
+
+    Supports only __init__, __repr__ and __hash__ methods.
+    Other list methods are available, but they may break contract.
+    """
+
+    def __init__(self, *args, **kwargs):
+        """Contruct super, sort and compute repr and hash cache values."""
+        sup = super(_Hsfl, self)
+        sup.__init__(*args, **kwargs)
+        sup.sort(key=repr)
+        self.__repr = repr(tuple(self))
+        self.__hash = hash(self.__repr)
+
+    def __repr__(self):
+        """Return cached repr string."""
+        return self.__repr
+
+    def __hash__(self):
+        """Return cached hash."""
+        return self.__hash
+
+
+class _Hsfod(_collections.OrderedDict):
+    """
+    Hashable sorted (by key) frozen OrderedDict implementation stub.
+
+    Supports only __init__, __repr__ and __hash__ methods.
+    Other OrderedDict methods are available, but they may break contract.
+    """
+
+    def __init__(self, *args, **kwargs):
+        """Put arguments to OrderedDict, sort, pass to super, cache values."""
+        self_unsorted = _collections.OrderedDict(*args, **kwargs)
+        items_sorted = sorted(self_unsorted.items(), key=repr)
+        sup = super(_Hsfod, self)  # possibly something else than OrderedDict
+        sup.__init__(items_sorted)
+        # Repr string is used for sorting, keys are more important than values.
+        self.__repr = '{' + repr(self.keys()) + ':' + repr(self.values()) + '}'
+        self.__hash = hash(self.__repr)
+
+    def __repr__(self):
+        """Return cached repr string."""
+        return self.__repr
+
+    def __hash__(self):
+        """Return cached hash."""
+        return self.__hash
+
+
+def _hsfl_array(s_and_end, scan_once, **kwargs):
+    """Scan JSON array as usual, but return hsfl instead of list."""
+    values, end = _json.decoder.JSONArray(s_and_end, scan_once, **kwargs)
+    return _Hsfl(values), end
+
+
+class _Decoder(_json.JSONDecoder):
+    """Private class to act as customized JSON decoder.
+
+    Based on: http://stackoverflow.com/questions/10885238/
+    python-change-list-type-for-json-decoding"""
+    def __init__(self, **kwargs):
+        """Initialize decoder with special array implementation."""
+        _json.JSONDecoder.__init__(self, **kwargs)
+        # Use the custom JSONArray
+        self.parse_array = _hsfl_array
+        # Use the python implemenation of the scanner
+        self.scan_once = _json.scanner.py_make_scanner(self)
+
+
+# Robot Keywords.
+
+
+def loads_sorted(text, strict=False):
+    """
+    Return Python object with sorted arrays and dictionary keys.
+
+    If strict is true, raise exception on parse error.
+    If strict is not true, return string with error message.
+    """
+    try:
+        object_decoded = _json.loads(text, cls=_Decoder, object_hook=_Hsfod)
+    except ValueError as err:
+        if strict:
+            raise err
+        else:
+            return str(err) + '\n' + text
+    return object_decoded
+
+
+def dumps_indented(obj, indent=1):
+    """
+    Wrapper for json.dumps with default indentation level. Adds newline.
+
+    The main value is that BuiltIn.Evaluate cannot easily accept Python object
+    as part of its argument.
+    Also, allows to use something different from RequestsLibrary.To_Json
+    """
+    pretty_json = _json.dumps(obj, separators=(',', ': '), indent=indent)
+    return pretty_json + '\n'  # to avoid diff "no newline" warning line
+
+
+def normalize_json_text(text, strict=False, indent=1):  # pylint likes lowercase
+    """Return sorted indented JSON string, or an error message string."""
+    object_decoded = loads_sorted(text, strict=strict)
+    pretty_json = dumps_indented(object_decoded, indent=indent)
+    return pretty_json
diff --git a/csit/suites/controller/OneNode_Datastore/carpeople_library_test.robot b/csit/suites/controller/OneNode_Datastore/carpeople_library_test.robot
new file mode 100644 (file)
index 0000000..d48f628
--- /dev/null
@@ -0,0 +1,97 @@
+*** Settings ***
+Documentation     Basic library verification suite, handling cars/people in 1-node setup.
+...
+...               Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+...
+...               This program and the accompanying materials are made available under the
+...               terms of the Eclipse Public License v1.0 which accompanies this distribution,
+...               and is available at http://www.eclipse.org/legal/epl-v10.html
+...
+...               This is a lightweight and stripped-down functional analogue of performance suite.
+...               Intention is to use this as a verify suite for changes in TemplatedRequests resource.
+Suite Setup       Start_Suite
+Test Setup        SetupUtils.Setup_Test_With_Logging_And_Without_Fast_Failing
+Test Teardown     SetupUtils.Teardown_Test_Show_Bugs_If_Test_Failed
+Variables         ${CURDIR}/../../../variables/Variables.py
+Resource          ${CURDIR}/../../../libraries/SetupUtils.robot
+Resource          ${CURDIR}/../../../libraries/TemplatedRequests.robot
+
+*** Variables ***
+${VAR_BASE}       ${CURDIR}/../../../variables/carpeople/libtest
+${BULK_SIZE}      2
+${TIMEOUT_BUG_4220}    60s
+
+*** Test Cases ***
+Wait_For_Rpcs
+    [Documentation]    Issue (invalid) RPC requests until 501 goes away (or timeout expires).
+    ...    See https://bugs.opendaylight.org/show_bug.cgi?id=4220
+    BuiltIn.Wait_Until_Keyword_Succeeds    ${TIMEOUT_BUG_4220}    5s    Check_Rpc_Readiness
+
+Add_And_Verify_Person
+    [Documentation]    Add a person entry, verify it is seen in the datastore.
+    TemplatedRequests.Put_As_Json_Templated    folder=${VAR_BASE}/person
+    TemplatedRequests.Get_As_Json_Templated    folder=${VAR_BASE}/person    verify=True
+
+Add_And_Verify_Car
+    [Documentation]    Add a car entry, verify it is seen in the datastore.
+    TemplatedRequests.Put_As_Json_Templated    folder=${VAR_BASE}/car
+    TemplatedRequests.Get_As_Json_Templated    folder=${VAR_BASE}/car    verify=True
+
+Purchase_And_Verify_Car
+    [Documentation]    Post RPC to buy a car.
+    TemplatedRequests.Post_As_Json_Templated    folder=${VAR_BASE}/purchase
+    TemplatedRequests.Get_As_Json_Templated    folder=${VAR_BASE}/car_person    verify=True
+
+Delete_CarPerson
+    [Documentation]    Remove the car-people entry from the datastore.
+    TemplatedRequests.Delete_Templated    folder=${VAR_BASE}/car_person
+    # TODO: Add specific error check on Get attempt.
+
+Delete_Car
+    [Documentation]    Remove the car entry from the datastore.
+    TemplatedRequests.Delete_Templated    folder=${VAR_BASE}/car
+    # TODO: Add specific error check on Get attempt.
+
+Delete_Person
+    [Documentation]    Remove the person entry from the datastore.
+    TemplatedRequests.Delete_Templated    folder=${VAR_BASE}/person
+    # TODO: Add specific error check on Get attempt.
+
+Add_And_Verify_People
+    [Documentation]    Create container with several people, verify it is seen in the datastore..
+    TemplatedRequests.Post_As_Json_Templated    folder=${VAR_BASE}/people    iterations=${BULK_SIZE}
+    TemplatedRequests.Get_As_Json_Templated    folder=${VAR_BASE}/people    verify=True    iterations=${BULK_SIZE}
+
+Add_And_Verify_Cars
+    [Documentation]    Create container with several cars, verify it is seen in the datastore.
+    TemplatedRequests.Post_As_Json_Templated    folder=${VAR_BASE}/cars    iterations=${BULK_SIZE}
+    TemplatedRequests.Get_As_Json_Templated    folder=${VAR_BASE}/cars    verify=True    iterations=${BULK_SIZE}
+    # TODO: Add cases with purchase loop.
+
+Delete_CarsPeople
+    [Documentation]    Remove cars-people container from the datastore.
+    TemplatedRequests.Delete_Templated    folder=${VAR_BASE}/cars_people
+    # TODO: Add specific error check on Get attempt.
+
+Delete_Cars
+    [Documentation]    Remove cars container from the datastore.
+    TemplatedRequests.Delete_Templated    folder=${VAR_BASE}/cars
+    # TODO: Add specific error check on Get attempt.
+
+Delete_People
+    [Documentation]    Remove people container from the datastore.
+    TemplatedRequests.Delete_Templated    folder=${VAR_BASE}/people
+    # TODO: Add specific error check on Get attempt.
+
+*** Keywords ***
+Start_Suite
+    [Documentation]    Suite setup keyword
+    SetupUtils.Setup_Utils_For_Setup_And_Teardown
+    TemplatedRequests.Create_Default_Session
+
+Check_Rpc_Readiness
+    [Documentation]    Issue invalid RPC requests and assert appropriate http status code
+    # So far only buy-car is checked, but other RPCs such as add-car may be added later.
+    ${status}    ${message} =    BuiltIn.Run_Keyword_And_Ignore_Error    TemplatedRequests.Post_As_Json_To_Uri    uri=restconf/operations/car-purchase:buy-car    data={"input":{}}
+    # TODO: Create template directory for this?
+    BuiltIn.Should_Not_Contain    ${message}    '50
index ab7576e5396beee8e31a4a7cd896295d31eec627..c2d5890738d30ad10f9ec103603222a375e39055 100644 (file)
@@ -5,6 +5,8 @@ integration/test/csit/suites/test/freeze.robot
 integration/test/csit/suites/test/libraries
 # Netconf connector readiness:
 integration/test/csit/suites/netconf/ready
+# 1-node car/people
+integration/test/csit/suites/controller/OneNode_Datastore/carpeople_library_test.robot
 # Openflow tests (no AD-SAL):
 integration/test/csit/suites/openflowplugin/MD_SAL_NSF_OF10
 integration/test/csit/suites/openflowplugin/MD_SAL_NSF_OF13
index de3ad9735ecf39b71f768afe1f963df806180fdd..1c384bcec722b19f837b6117012a1f36799cfa99 100644 (file)
@@ -176,6 +176,7 @@ HEADERS_YANG_JSON = {'Content-Type': 'application/yang.data+json'}
 HEADERS_XML = {'Content-Type': 'application/xml'}
 ACCEPT_XML = {'Accept': 'application/xml'}
 ACCEPT_JSON = {'Accept': 'application/json'}
+ACCEPT_EMPTY = {}  # Json should be default, but no-output RPC cannot have Accept header.
 ODL_CONTROLLER_SESSION = None
 TOPO_TREE_LEVEL = 2
 TOPO_TREE_DEPTH = 3
diff --git a/csit/variables/carpeople/libtest/car/data.json b/csit/variables/carpeople/libtest/car/data.json
new file mode 100644 (file)
index 0000000..8dbcd40
--- /dev/null
@@ -0,0 +1,7 @@
+{
+ "car-entry": [
+  {
+   "id": "boogie"
+  }
+ ]
+}
diff --git a/csit/variables/carpeople/libtest/car/location.uri b/csit/variables/carpeople/libtest/car/location.uri
new file mode 100644 (file)
index 0000000..e07c8f8
--- /dev/null
@@ -0,0 +1 @@
+/restconf/config/car:cars/car-entry/boogie
diff --git a/csit/variables/carpeople/libtest/car_person/data.json b/csit/variables/carpeople/libtest/car_person/data.json
new file mode 100644 (file)
index 0000000..e538c28
--- /dev/null
@@ -0,0 +1,8 @@
+{
+ "car-person": [
+  {
+   "car-id": "boogie",
+   "person-id": "joe",
+  }
+ ]
+}
diff --git a/csit/variables/carpeople/libtest/car_person/location.uri b/csit/variables/carpeople/libtest/car_person/location.uri
new file mode 100644 (file)
index 0000000..49d3e33
--- /dev/null
@@ -0,0 +1 @@
+/restconf/config/car-people:car-people/car-person/boogie/joe
diff --git a/csit/variables/carpeople/libtest/cars/data.epilog.json b/csit/variables/carpeople/libtest/cars/data.epilog.json
new file mode 100644 (file)
index 0000000..e633845
--- /dev/null
@@ -0,0 +1,3 @@
+  ]
+ }
+}
diff --git a/csit/variables/carpeople/libtest/cars/data.item.json b/csit/variables/carpeople/libtest/cars/data.item.json
new file mode 100644 (file)
index 0000000..4f1c2da
--- /dev/null
@@ -0,0 +1,3 @@
+   {
+    "id": "car-$i"
+   }
diff --git a/csit/variables/carpeople/libtest/cars/data.prolog.json b/csit/variables/carpeople/libtest/cars/data.prolog.json
new file mode 100644 (file)
index 0000000..632c77c
--- /dev/null
@@ -0,0 +1,3 @@
+{
+ "cars": {
+  "car-entry": [
diff --git a/csit/variables/carpeople/libtest/cars/location.uri b/csit/variables/carpeople/libtest/cars/location.uri
new file mode 100644 (file)
index 0000000..22a7f80
--- /dev/null
@@ -0,0 +1 @@
+/restconf/config/car:cars
diff --git a/csit/variables/carpeople/libtest/cars/post_data.epilog.json b/csit/variables/carpeople/libtest/cars/post_data.epilog.json
new file mode 100644 (file)
index 0000000..fffc6c7
--- /dev/null
@@ -0,0 +1,2 @@
+ ]
+}
diff --git a/csit/variables/carpeople/libtest/cars/post_data.prolog.json b/csit/variables/carpeople/libtest/cars/post_data.prolog.json
new file mode 100644 (file)
index 0000000..fe01eea
--- /dev/null
@@ -0,0 +1,2 @@
+{
+ "car-entry": [
diff --git a/csit/variables/carpeople/libtest/cars_people/location.uri b/csit/variables/carpeople/libtest/cars_people/location.uri
new file mode 100644 (file)
index 0000000..81ac444
--- /dev/null
@@ -0,0 +1 @@
+/restconf/config/car-people:car-people
diff --git a/csit/variables/carpeople/libtest/people/data.epilog.json b/csit/variables/carpeople/libtest/people/data.epilog.json
new file mode 100644 (file)
index 0000000..e633845
--- /dev/null
@@ -0,0 +1,3 @@
+  ]
+ }
+}
diff --git a/csit/variables/carpeople/libtest/people/data.item.json b/csit/variables/carpeople/libtest/people/data.item.json
new file mode 100644 (file)
index 0000000..4226128
--- /dev/null
@@ -0,0 +1,3 @@
+   {
+    "id": "person-$i"
+   }
diff --git a/csit/variables/carpeople/libtest/people/data.prolog.json b/csit/variables/carpeople/libtest/people/data.prolog.json
new file mode 100644 (file)
index 0000000..31975cb
--- /dev/null
@@ -0,0 +1,3 @@
+{
+ "people": {
+  "person": [
diff --git a/csit/variables/carpeople/libtest/people/location.uri b/csit/variables/carpeople/libtest/people/location.uri
new file mode 100644 (file)
index 0000000..91229c8
--- /dev/null
@@ -0,0 +1 @@
+/restconf/config/people:people
diff --git a/csit/variables/carpeople/libtest/people/post_data.epilog.json b/csit/variables/carpeople/libtest/people/post_data.epilog.json
new file mode 100644 (file)
index 0000000..fffc6c7
--- /dev/null
@@ -0,0 +1,2 @@
+ ]
+}
diff --git a/csit/variables/carpeople/libtest/people/post_data.prolog.json b/csit/variables/carpeople/libtest/people/post_data.prolog.json
new file mode 100644 (file)
index 0000000..ee5a303
--- /dev/null
@@ -0,0 +1,2 @@
+{
+ "person": [
diff --git a/csit/variables/carpeople/libtest/person/data.json b/csit/variables/carpeople/libtest/person/data.json
new file mode 100644 (file)
index 0000000..92511d2
--- /dev/null
@@ -0,0 +1,7 @@
+{
+ "person": [
+  {
+   "id": "joe"
+  }
+ ]
+}
diff --git a/csit/variables/carpeople/libtest/person/location.uri b/csit/variables/carpeople/libtest/person/location.uri
new file mode 100644 (file)
index 0000000..f8669be
--- /dev/null
@@ -0,0 +1 @@
+/restconf/config/people:people/person/joe
diff --git a/csit/variables/carpeople/libtest/purchase/location.uri b/csit/variables/carpeople/libtest/purchase/location.uri
new file mode 100644 (file)
index 0000000..503b42c
--- /dev/null
@@ -0,0 +1 @@
+/restconf/operations/car-purchase:buy-car
diff --git a/csit/variables/carpeople/libtest/purchase/post_data.json b/csit/variables/carpeople/libtest/purchase/post_data.json
new file mode 100644 (file)
index 0000000..df97195
--- /dev/null
@@ -0,0 +1,7 @@
+{
+ "input": {
+  "car-id": "boogie",
+  "person-id": "joe",
+  "person": "/people:people/people:person[people:id='joe']"
+ }
+}