Integrate DataQuery

Change-Id: I439374afe485baf08a4d5a1a02fd70d908bec9e1
diff --git a/tests/datastoreaccess_mock.hpp b/tests/datastoreaccess_mock.hpp
new file mode 100644
index 0000000..289d919
--- /dev/null
+++ b/tests/datastoreaccess_mock.hpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 CESNET, https://photonics.cesnet.cz/
+ *
+ * Written by Václav Kubernát <kubernat@cesnet.cz>
+ *
+*/
+
+#include <map>
+#include "datastore_access.hpp"
+#include "trompeloeil_doctest.h"
+#include "utils.hpp"
+
+namespace trompeloeil {
+    template <>
+    inline void print(std::ostream& os, const leaf_data_& data)
+    {
+        os << leafDataToString(data);
+    }
+}
+
+class MockDatastoreAccess : public trompeloeil::mock_interface<DatastoreAccess> {
+    IMPLEMENT_MOCK1(getItems);
+    IMPLEMENT_MOCK2(setLeaf);
+    IMPLEMENT_MOCK1(createPresenceContainer);
+    IMPLEMENT_MOCK1(deletePresenceContainer);
+    IMPLEMENT_MOCK1(createListInstance);
+    IMPLEMENT_MOCK1(deleteListInstance);
+    IMPLEMENT_MOCK2(executeRpc);
+
+    // Can't use IMPLEMENT_MOCK for private methods - IMPLEMENT_MOCK needs full visibility of the method
+    MAKE_MOCK1(listInstances, std::vector<ListInstance>(const std::string&), override);
+
+
+    IMPLEMENT_MOCK0(schema);
+
+    IMPLEMENT_MOCK0(commitChanges);
+    IMPLEMENT_MOCK0(discardChanges);
+};
+
+
diff --git a/tests/enum_completion.cpp b/tests/enum_completion.cpp
index 29f23a7..c6b31d8 100644
--- a/tests/enum_completion.cpp
+++ b/tests/enum_completion.cpp
@@ -8,6 +8,7 @@
  */
 
 #include "trompeloeil_doctest.h"
+#include "datastoreaccess_mock.hpp"
 #include "parser.hpp"
 #include "pretty_printers.hpp"
 #include "static_schema.hpp"
@@ -22,7 +23,16 @@
     schema->addList("/", "mod:list", {"number"});
     schema->addLeaf("/mod:list", "mod:number", yang::LeafDataTypes::Int32);
     schema->addLeafEnum("/mod:list", "mod:leafInList", {"ano", "anoda", "ne", "katoda"});
-    Parser parser(schema);
+    auto mockDatastore = std::make_shared<MockDatastoreAccess>();
+    // The parser will use DataQuery for key value completion, but I'm not testing that here, so I don't return anything.
+    ALLOW_CALL(*mockDatastore, listInstances("/mod:list"))
+        .RETURN(std::vector<ListInstance>{});
+
+    // DataQuery gets the schema from DatastoreAccess once
+    auto expectation = NAMED_REQUIRE_CALL(*mockDatastore, schema())
+        .RETURN(schema);
+    auto dataQuery = std::make_shared<DataQuery>(*mockDatastore);
+    Parser parser(schema, dataQuery);
     std::string input;
     std::ostringstream errorStream;
 
diff --git a/tests/keyvalue_completion.cpp b/tests/keyvalue_completion.cpp
new file mode 100644
index 0000000..56c7841
--- /dev/null
+++ b/tests/keyvalue_completion.cpp
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2020 CESNET, https://photonics.cesnet.cz/
+ *
+ * Written by Václav Kubernát <kubervac@fit.cvut.cz>
+ *
+*/
+
+#include <experimental/iterator>
+#include <iostream>
+#include "trompeloeil_doctest.h"
+#include "ast_commands.hpp"
+#include "datastoreaccess_mock.hpp"
+#include "parser.hpp"
+#include "static_schema.hpp"
+
+namespace std {
+std::ostream& operator<<(std::ostream& s, const std::set<std::string> set)
+{
+    s << std::endl << "{";
+    std::copy(set.begin(), set.end(), std::experimental::make_ostream_joiner(s, ", "));
+    s << "}" << std::endl;
+    return s;
+}
+}
+
+TEST_CASE("keyvalue_completion")
+{
+    auto schema = std::make_shared<StaticSchema>();
+    schema->addModule("example");
+    schema->addContainer("/", "example:a");
+    schema->addContainer("/", "example:b");
+    schema->addList("/", "example:list", {"number"});
+    schema->addLeaf("/example:list", "example:number", yang::LeafDataTypes::Int32);
+    schema->addList("/", "example:twoKeyList", {"number", "name"});
+    schema->addLeaf("/example:twoKeyList", "example:number", yang::LeafDataTypes::Int32);
+    schema->addLeaf("/example:twoKeyList", "example:name", yang::LeafDataTypes::String);
+    auto mockDatastore = std::make_shared<MockDatastoreAccess>();
+
+    // DataQuery gets the schema from DatastoreAccess once
+    auto expectation = NAMED_REQUIRE_CALL(*mockDatastore, schema())
+        .RETURN(schema);
+    auto dataQuery = std::make_shared<DataQuery>(*mockDatastore);
+    expectation.reset();
+    Parser parser(schema, dataQuery);
+    std::string input;
+    std::ostringstream errorStream;
+
+    std::set<std::string> expected;
+    std::vector<std::shared_ptr<trompeloeil::expectation>> queryExpectations;
+    std::vector<ListInstance> queryReturn;
+
+    SECTION("get example:list[number=")
+    {
+        input = "get example:list[number=";
+        queryReturn = {
+            {{"number", 1}},
+            {{"number", 7}},
+            {{"number", 9}},
+            {{"number", 42}}
+        };
+        expected = {
+            "1",
+            "7",
+            "9",
+            "42"
+        };
+        queryExpectations.push_back(NAMED_REQUIRE_CALL(*mockDatastore, listInstances("/example:list"))
+            .RETURN(queryReturn));
+    }
+
+    SECTION("get example:twoKeyList[number=")
+    {
+        queryReturn = {
+            {{"number", 1}, {"name", std::string{"Petr"}}},
+            {{"number", 7}, {"name", std::string{"Petr"}}},
+            {{"number", 10}, {"name", std::string{"Petr"}}},
+            {{"number", 10}, {"name", std::string{"Honza"}}},
+            {{"number", 100}, {"name", std::string{"Honza"}}},
+        };
+        input = "get example:twoKeyList[";
+        SECTION("no keys set")
+        {
+            SECTION("number")
+            {
+                input += "number=";
+                expected = { "1", "7", "10", "100" };
+            }
+            SECTION("name")
+            {
+                input += "name=";
+                expected = { "'Petr'", "'Honza'"};
+            }
+            queryExpectations.push_back(NAMED_REQUIRE_CALL(*mockDatastore, listInstances("/example:twoKeyList"))
+                    .RETURN(queryReturn));
+        }
+
+        SECTION("name is set")
+        {
+            input += "name=";
+            SECTION("Petr")
+            {
+                input += "'Petr'";
+                expected = { "1", "7", "10"};
+            }
+            SECTION("Honza")
+            {
+                input += "'Honza'";
+                expected = { "10", "100" };
+            }
+            input += "][number=";
+            queryExpectations.push_back(NAMED_REQUIRE_CALL(*mockDatastore, listInstances("/example:twoKeyList"))
+                    .TIMES(2)
+                    .RETURN(queryReturn));
+        }
+
+        SECTION("number is set")
+        {
+            input += "number=";
+            SECTION("1")
+            {
+                input += "1";
+                expected = { "'Petr'" };
+            }
+            SECTION("7")
+            {
+                input += "7";
+                expected = { "'Petr'" };
+            }
+            SECTION("10")
+            {
+                input += "10";
+                expected = { "'Honza'", "'Petr'" };
+            }
+            SECTION("100")
+            {
+                input += "100";
+                expected = { "'Honza'" };
+            }
+            input += "][name=";
+            queryExpectations.push_back(NAMED_REQUIRE_CALL(*mockDatastore, listInstances("/example:twoKeyList"))
+                    .TIMES(2)
+                    .RETURN(queryReturn));
+        }
+
+        SECTION("both keys are set")
+        {
+            SECTION("get example:twoKeyList[number=123][name='Petr'")
+            {
+                input = "get example:twoKeyList[number=123][name='Petr'";
+                expected = {"]/"};
+            }
+            SECTION("get example:twoKeyList[number=123][name='Petr']")
+            {
+                input = "get example:twoKeyList[number=123][name='Petr']";
+                expected = {"]/"};
+            }
+            SECTION("get example:twoKeyList[number=123][name='Petr'][")
+            {
+                input = "get example:twoKeyList[number=123][name='Petr'][";
+            }
+            // I use ALLOW_CALL here, all this stuff calls it different number of times
+            queryExpectations.push_back(NAMED_ALLOW_CALL(*mockDatastore, listInstances("/example:twoKeyList"))
+                    .RETURN(queryReturn));
+        }
+
+    }
+
+    REQUIRE(parser.completeCommand(input, errorStream).m_completions == expected);
+    for (auto& it : queryExpectations) {
+        it.reset();
+    }
+}
diff --git a/tests/path_completion.cpp b/tests/path_completion.cpp
index 832110d..0ad6089 100644
--- a/tests/path_completion.cpp
+++ b/tests/path_completion.cpp
@@ -7,6 +7,7 @@
 */
 
 #include "trompeloeil_doctest.h"
+#include "datastoreaccess_mock.hpp"
 #include "parser.hpp"
 #include "pretty_printers.hpp"
 #include "static_schema.hpp"
@@ -36,7 +37,20 @@
     schema->addLeaf("/example:twoKeyList", "example:name", yang::LeafDataTypes::String);
     schema->addLeaf("/example:twoKeyList", "example:number", yang::LeafDataTypes::Int32);
     schema->addLeaf("/", "example:leafInt", yang::LeafDataTypes::Int32);
-    Parser parser(schema);
+    auto mockDatastore = std::make_shared<MockDatastoreAccess>();
+
+    // The parser will use DataQuery for key value completion, but I'm not testing that here, so I don't return anything.
+    ALLOW_CALL(*mockDatastore, listInstances("/example:list"))
+        .RETURN(std::vector<ListInstance>{});
+    ALLOW_CALL(*mockDatastore, listInstances("/example:twoKeyList"))
+        .RETURN(std::vector<ListInstance>{});
+
+    // DataQuery gets the schema from DatastoreAccess once
+    auto expectation = NAMED_REQUIRE_CALL(*mockDatastore, schema())
+        .RETURN(schema);
+    auto dataQuery = std::make_shared<DataQuery>(*mockDatastore);
+    expectation.reset();
+    Parser parser(schema, dataQuery);
     std::string input;
     std::ostringstream errorStream;
 
@@ -188,8 +202,8 @@
         SECTION("cd example:list[number=")
         {
             input = "cd example:list[number=";
-            expectedCompletions = {"number="};
-            expectedContextLength = 7;
+            expectedCompletions = {};
+            expectedContextLength = 0;
         }
 
         SECTION("cd example:list[number=12")