From b1fe8647799b2bd01983b0beeaca62be57a23014 Mon Sep 17 00:00:00 2001 From: Nikita Poltorapavlo Date: Fri, 2 Feb 2024 17:50:50 +0200 Subject: [PATCH] test --- .github/workflows/L0-PersistentStore-grpc.yml | 53 ++ ...istentStore.yml => L0-PersistentStore.yml} | 28 +- ...lite.yml => L1-PersistentStore-sqlite.yml} | 2 +- PersistentStore/CMakeLists.txt | 11 +- PersistentStore/PersistentStore.cpp | 58 +-- PersistentStore/PersistentStore.h | 3 + PersistentStore/grpc/Store2Type.h | 240 +++++++++ PersistentStore/grpc/Stub.h | 48 ++ PersistentStore/grpc/l0test/CMakeLists.txt | 107 ++++ .../grpc/l0test/SecureStorageServiceMock.h | 14 + PersistentStore/grpc/l0test/Server.h | 20 + PersistentStore/grpc/l0test/Store2Test.cpp | 183 +++++++ PersistentStore/grpc/l0test/StubTest.cpp | 277 ++++++++++ PersistentStore/grpc/secure_storage.proto | 136 +++++ .../{l1test => l0test}/CMakeLists.txt | 23 +- .../l0test/PersistentStoreJsonRpcTest.cpp | 492 ++++++++++++++++++ .../l0test/PersistentStoreStoreTest.cpp | 151 ++++++ .../{l1test => l0test}/PluginTest.cpp | 0 .../{l1test => l0test}/ServiceMock.h | 12 +- PersistentStore/l0test/Store2Mock.h | 18 + PersistentStore/l0test/StoreCacheMock.h | 13 + PersistentStore/l0test/StoreInspectorMock.h | 15 + PersistentStore/l0test/StoreLimitMock.h | 14 + PersistentStore/l1test/JsonRpcTest.cpp | 265 ---------- PersistentStore/l1test/StoreTest.cpp | 149 ------ .../sqlite/ServiceRegistration.cpp | 48 ++ 26 files changed, 1881 insertions(+), 499 deletions(-) create mode 100644 .github/workflows/L0-PersistentStore-grpc.yml rename .github/workflows/{L1-PersistentStore.yml => L0-PersistentStore.yml} (54%) rename .github/workflows/{L1-Sqlite.yml => L1-PersistentStore-sqlite.yml} (98%) create mode 100644 PersistentStore/grpc/Store2Type.h create mode 100644 PersistentStore/grpc/Stub.h create mode 100644 PersistentStore/grpc/l0test/CMakeLists.txt create mode 100644 PersistentStore/grpc/l0test/SecureStorageServiceMock.h create mode 100644 PersistentStore/grpc/l0test/Server.h create mode 100644 PersistentStore/grpc/l0test/Store2Test.cpp create mode 100644 PersistentStore/grpc/l0test/StubTest.cpp create mode 100644 PersistentStore/grpc/secure_storage.proto rename PersistentStore/{l1test => l0test}/CMakeLists.txt (76%) create mode 100644 PersistentStore/l0test/PersistentStoreJsonRpcTest.cpp create mode 100644 PersistentStore/l0test/PersistentStoreStoreTest.cpp rename PersistentStore/{l1test => l0test}/PluginTest.cpp (100%) rename PersistentStore/{l1test => l0test}/ServiceMock.h (93%) create mode 100644 PersistentStore/l0test/Store2Mock.h create mode 100644 PersistentStore/l0test/StoreCacheMock.h create mode 100644 PersistentStore/l0test/StoreInspectorMock.h create mode 100644 PersistentStore/l0test/StoreLimitMock.h delete mode 100644 PersistentStore/l1test/JsonRpcTest.cpp delete mode 100644 PersistentStore/l1test/StoreTest.cpp create mode 100644 PersistentStore/sqlite/ServiceRegistration.cpp diff --git a/.github/workflows/L0-PersistentStore-grpc.yml b/.github/workflows/L0-PersistentStore-grpc.yml new file mode 100644 index 0000000000..d1a06a345c --- /dev/null +++ b/.github/workflows/L0-PersistentStore-grpc.yml @@ -0,0 +1,53 @@ +name: L0-PersistentStore-grpc + +on: + push: + paths: + - PersistentStore/grpc/** + pull_request: + paths: + - PersistentStore/grpc/** + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + path: ${{github.repository}} + + - name: Install valgrind, coverage, cmake, protoc, grpc_cpp_plugin, grpc + run: | + sudo apt update + sudo apt install -y valgrind lcov cmake protobuf-compiler protobuf-compiler-grpc libgrpc++-dev + + - name: Build Thunder + working-directory: ${{github.workspace}} + run: sh +x ${GITHUB_REPOSITORY}/.github/workflows/BuildThunder.sh + + - name: Build + working-directory: ${{github.workspace}} + run: | + cmake -S ${GITHUB_REPOSITORY}/PersistentStore/grpc/l0test -B build/persistentstoregrpcl0test -DCMAKE_INSTALL_PREFIX="install/usr" -DCMAKE_CXX_FLAGS="--coverage -Wall -Werror" + cmake --build build/persistentstoregrpcl0test --target install + + - name: Run + working-directory: ${{github.workspace}} + run: PATH=${PWD}/install/usr/bin:${PATH} LD_LIBRARY_PATH=${PWD}/install/usr/lib:${LD_LIBRARY_PATH} valgrind --tool=memcheck --log-file=valgrind_log --leak-check=yes --show-reachable=yes --track-fds=yes --fair-sched=try grpcl0test + + - name: Generate coverage + working-directory: ${{github.workspace}} + run: | + lcov -c -o coverage.info -d build/persistentstoregrpcl0test + genhtml -o coverage coverage.info + + - name: Upload artifacts + if: ${{ !env.ACT }} + uses: actions/upload-artifact@v4 + with: + name: artifacts + path: | + coverage/ + valgrind_log + if-no-files-found: warn diff --git a/.github/workflows/L1-PersistentStore.yml b/.github/workflows/L0-PersistentStore.yml similarity index 54% rename from .github/workflows/L1-PersistentStore.yml rename to .github/workflows/L0-PersistentStore.yml index 5a5c391d89..1b68bf704f 100644 --- a/.github/workflows/L1-PersistentStore.yml +++ b/.github/workflows/L0-PersistentStore.yml @@ -1,4 +1,4 @@ -name: L1-PersistentStore +name: L0-PersistentStore on: push: @@ -13,43 +13,41 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: - path: rdkservices + path: ${{github.repository}} - - name: Install valgrind, coverage, cmake, sqlite + - name: Install valgrind, coverage, cmake run: | sudo apt update - sudo apt install -y valgrind lcov cmake libsqlite3-dev + sudo apt install -y valgrind lcov cmake - name: Build Thunder working-directory: ${{github.workspace}} - run: sh +x rdkservices/.github/workflows/BuildThunder.sh - - - name: Configure - working-directory: ${{github.workspace}} - run: cmake -S rdkservices/PersistentStore/l1test -B build/persistentstorel1test -DCMAKE_INSTALL_PREFIX="install/usr" -DCMAKE_CXX_FLAGS="--coverage -Wall -Werror" + run: sh +x ${GITHUB_REPOSITORY}/.github/workflows/BuildThunder.sh - name: Build working-directory: ${{github.workspace}} - run: cmake --build build/persistentstorel1test --target install + run: | + cmake -S ${GITHUB_REPOSITORY}/PersistentStore/l0test -B build/persistentstorel0test -DCMAKE_INSTALL_PREFIX="install/usr" -DCMAKE_CXX_FLAGS="--coverage -Wall -Werror" + cmake --build build/persistentstorel0test --target install - name: Run working-directory: ${{github.workspace}} - run: PATH=${PWD}/install/usr/bin:${PATH} LD_LIBRARY_PATH=${PWD}/install/usr/lib:${LD_LIBRARY_PATH} valgrind --tool=memcheck --log-file=valgrind_log --leak-check=yes --show-reachable=yes --track-fds=yes --fair-sched=try persistentstorel1test + run: PATH=${PWD}/install/usr/bin:${PATH} LD_LIBRARY_PATH=${PWD}/install/usr/lib:${LD_LIBRARY_PATH} valgrind --tool=memcheck --log-file=valgrind_log --leak-check=yes --show-reachable=yes --track-fds=yes --fair-sched=try persistentstorel0test - name: Generate coverage working-directory: ${{github.workspace}} run: | - lcov -c -o coverage.info -d build/persistentstorel1test + lcov -c -o coverage.info -d build/persistentstorel0test genhtml -o coverage coverage.info - name: Upload artifacts if: ${{ !env.ACT }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: artifacts path: | coverage/ valgrind_log - if-no-files-found: warn \ No newline at end of file + if-no-files-found: warn diff --git a/.github/workflows/L1-Sqlite.yml b/.github/workflows/L1-PersistentStore-sqlite.yml similarity index 98% rename from .github/workflows/L1-Sqlite.yml rename to .github/workflows/L1-PersistentStore-sqlite.yml index 12fed1aa4e..7a1ec29fc3 100644 --- a/.github/workflows/L1-Sqlite.yml +++ b/.github/workflows/L1-PersistentStore-sqlite.yml @@ -1,4 +1,4 @@ -name: L1-Sqlite +name: L1-PersistentStore-sqlite on: push: diff --git a/PersistentStore/CMakeLists.txt b/PersistentStore/CMakeLists.txt index 848d6697e5..2240bf4f7b 100644 --- a/PersistentStore/CMakeLists.txt +++ b/PersistentStore/CMakeLists.txt @@ -23,7 +23,7 @@ find_package(WPEFramework) project_version(1.0.0) -set(MODULE_NAME ${NAMESPACE}${PROJECT_NAME}) +set(MODULE_NAME ${NAMESPACE} ${PROJECT_NAME}) message("Setup ${MODULE_NAME} v${PROJECT_VERSION}") @@ -55,13 +55,14 @@ add_library(${MODULE_NAME} SHARED PersistentStore.cpp PersistentStoreJsonRpc.cpp sqlite/Handle.cpp + sqlite/ServiceRegistration.cpp Module.cpp - ) +) set_target_properties(${MODULE_NAME} PROPERTIES CXX_STANDARD 11 CXX_STANDARD_REQUIRED YES - ) +) add_definitions(${SQLITE_CFLAGS_OTHER}) link_directories(${SQLITE_LIBRARY_DIRS}) @@ -71,14 +72,14 @@ target_include_directories(${MODULE_NAME} PRIVATE ${SQLITE_INCLUDE_DIRS} ${IARMBUS_INCLUDE_DIRS} ${IARMSYS_INCLUDE_DIRS} - ) +) target_link_libraries(${MODULE_NAME} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins ${NAMESPACE}Definitions::${NAMESPACE}Definitions ${SQLITE_LIBRARIES} ${IARMBUS_LIBRARIES} - ) +) install(TARGETS ${MODULE_NAME} DESTINATION lib/${STORAGE_DIRECTORY}/plugins) diff --git a/PersistentStore/PersistentStore.cpp b/PersistentStore/PersistentStore.cpp index e3b92030ab..0381bba795 100644 --- a/PersistentStore/PersistentStore.cpp +++ b/PersistentStore/PersistentStore.cpp @@ -18,17 +18,6 @@ */ #include "PersistentStore.h" - -#include "sqlite/Handle.h" -#include "sqlite/Store2Type.h" -#include "sqlite/Store2WithReconnectType.h" -#if defined(WITH_CLOCK_SYNC) -#include "sqlite/Store2WithClockSyncType.h" -#endif -#include "sqlite/StoreCacheType.h" -#include "sqlite/StoreInspectorType.h" -#include "sqlite/StoreLimitType.h" - #include #define API_VERSION_NUMBER_MAJOR 1 @@ -52,25 +41,6 @@ namespace { namespace Plugin { -#if defined(WITH_CLOCK_SYNC) - class SqliteStore2 : public Sqlite::Store2WithClockSyncType>> { - }; -#else - class SqliteStore2 : public Sqlite::Store2WithReconnectType> { - }; -#endif - class SqliteStoreCache : public Sqlite::StoreCacheType { - }; - class SqliteStoreInspector : public Sqlite::StoreInspectorType { - }; - class SqliteStoreLimit : public Sqlite::StoreLimitType { - }; - - SERVICE_REGISTRATION(SqliteStore2, 1, 0); - SERVICE_REGISTRATION(SqliteStoreCache, 1, 0); - SERVICE_REGISTRATION(SqliteStoreInspector, 1, 0); - SERVICE_REGISTRATION(SqliteStoreLimit, 1, 0); - SERVICE_REGISTRATION(PersistentStore, API_VERSION_NUMBER_MAJOR, API_VERSION_NUMBER_MINOR, API_VERSION_NUMBER_PATCH); const string PersistentStore::Initialize(PluginHost::IShell* service) @@ -82,16 +52,12 @@ namespace Plugin { auto configLine = service->ConfigLine(); _config.FromString(configLine); - ASSERT(!_config.Path.Value().empty()); - { Core::File file(_config.Path.Value()); Core::File oldFile(_config.LegacyPath.Value()); if (file.Name() != oldFile.Name()) { if (oldFile.Exists()) { if (!file.Exists()) { - // Rename will fail if the files are not on the same filesystem - // Make a copy and then remove the original file std::ifstream in(oldFile.Name(), std::ios::in | std::ios::binary); std::ofstream out(file.Name(), std::ios::out | std::ios::binary); out << in.rdbuf(); @@ -109,15 +75,27 @@ namespace Plugin { uint32_t connectionId; - Store2::ScopeMapType initList1 = { - { Exchange::IStore2::ScopeType::DEVICE, service->Root(connectionId, 2000, _T("SqliteStore2")) } - }; + Store2::ScopeMapType initList1; + auto deviceStore2 = service->Root(connectionId, 2000, _T("DeviceStore2")); + if (deviceStore2 != nullptr) { + initList1.emplace(Exchange::IStore2::ScopeType::DEVICE, deviceStore2); + } + auto accountStore2 = service->Root(connectionId, 2000, _T("AccountStore2")); + if (accountStore2 != nullptr) { + initList1.emplace(Exchange::IStore2::ScopeType::ACCOUNT, accountStore2); + } _store2 = Core::Service::Create(initList1); + if (deviceStore2 != nullptr) { + deviceStore2->Release(); + } + if (accountStore2 != nullptr) { + accountStore2->Release(); + } _store2->Register(&_store2Sink); _store = Core::Service::Create(_store2); - _storeCache = service->Root(connectionId, 2000, _T("SqliteStoreCache")); - _storeInspector = service->Root(connectionId, 2000, _T("SqliteStoreInspector")); - _storeLimit = service->Root(connectionId, 2000, _T("SqliteStoreLimit")); + _storeCache = service->Root(connectionId, 2000, _T("DeviceStoreCache")); + _storeInspector = service->Root(connectionId, 2000, _T("DeviceStoreInspector")); + _storeLimit = service->Root(connectionId, 2000, _T("DeviceStoreLimit")); return result; } diff --git a/PersistentStore/PersistentStore.h b/PersistentStore/PersistentStore.h index 1c8d3b96af..e0188bc33d 100644 --- a/PersistentStore/PersistentStore.h +++ b/PersistentStore/PersistentStore.h @@ -224,6 +224,9 @@ namespace Plugin { Store2(const ScopeMapType& map) : _scopeMap(map) { + for (auto const& x : _scopeMap) { + x.second->AddRef(); + } } ~Store2() override { diff --git a/PersistentStore/grpc/Store2Type.h b/PersistentStore/grpc/Store2Type.h new file mode 100644 index 0000000000..c869bb8e1e --- /dev/null +++ b/PersistentStore/grpc/Store2Type.h @@ -0,0 +1,240 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2022 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../Module.h" +#include "secure_storage.grpc.pb.h" +#include + +namespace WPEFramework { +namespace Plugin { + namespace Grpc { + + template + class Store2Type : public Exchange::IStore2, protected ACTUALSTUB { + private: + Store2Type(const Store2Type&) = delete; + Store2Type& operator=(const Store2Type&) = delete; + + public: + template + Store2Type(Args&&... args) + : Exchange::IStore2() + , ACTUALSTUB(std::forward(args)...) + , _clients() + , _clientLock() + { + } + ~Store2Type() override = default; + + public: + // IStore2 methods + + uint32_t Register(Exchange::IStore2::INotification* notification) override + { + Core::SafeSyncType lock(_clientLock); + + ASSERT(std::find(_clients.begin(), _clients.end(), notification) == _clients.end()); + + notification->AddRef(); + _clients.push_back(notification); + + return Core::ERROR_NONE; + } + uint32_t Unregister(Exchange::IStore2::INotification* notification) override + { + Core::SafeSyncType lock(_clientLock); + + std::list::iterator + index(std::find(_clients.begin(), _clients.end(), notification)); + + ASSERT(index != _clients.end()); + + if (index != _clients.end()) { + notification->Release(); + _clients.erase(index); + } + + return Core::ERROR_NONE; + } + + uint32_t SetValue(const ScopeType scope, const string& ns, const string& key, const string& value, const uint32_t ttl) override + { + ASSERT(scope == ScopeType::ACCOUNT); + + uint32_t result; + + grpc::ClientContext context; + + distp::gateway::secure_storage::v1::UpdateValueRequest request; + auto v = new distp::gateway::secure_storage::v1::Value(); + v->set_value(value); + if (ttl != 0) { + auto t = new google::protobuf::Duration(); + t->set_seconds(ttl); + v->set_allocated_ttl(t); + } + auto k = new distp::gateway::secure_storage::v1::Key(); + k->set_app_id(ns); + k->set_key(key); + k->set_scope(distp::gateway::secure_storage::v1::Scope::SCOPE_ACCOUNT); + v->set_allocated_key(k); + request.set_allocated_value(v); + + distp::gateway::secure_storage::v1::UpdateValueResponse response; + + auto status = (*this)->UpdateValue(&context, request, &response); + if (status.ok()) { + OnValueChanged(ns, key, value); + result = Core::ERROR_NONE; + } else { + OnError(__FUNCTION__, status); + result = Core::ERROR_GENERAL; + } + + return result; + } + uint32_t GetValue(const ScopeType scope, const string& ns, const string& key, string& value, uint32_t& ttl) override + { + ASSERT(scope == ScopeType::ACCOUNT); + + uint32_t result; + + grpc::ClientContext context; + + distp::gateway::secure_storage::v1::GetValueRequest request; + auto k = new distp::gateway::secure_storage::v1::Key(); + k->set_app_id(ns); + k->set_key(key); + k->set_scope(distp::gateway::secure_storage::v1::Scope::SCOPE_ACCOUNT); + request.set_allocated_key(k); + + distp::gateway::secure_storage::v1::GetValueResponse response; + + auto status = (*this)->GetValue(&context, request, &response); + if (status.ok()) { + if (response.has_value()) { + auto v = response.value(); + if (v.has_ttl()) { + value = v.value(); + ttl = v.ttl().seconds(); + result = Core::ERROR_NONE; + } else if (v.has_expire_time()) { + value = v.value(); + ttl = v.expire_time().seconds() - (Core::Time::Now().Ticks() / Core::Time::TicksPerMillisecond / 1000); + result = Core::ERROR_NONE; + } else { + value = v.value(); + ttl = 0; + result = Core::ERROR_NONE; + } + } else { + OnError(__FUNCTION__, status); + result = Core::ERROR_GENERAL; + } + } else { + OnError(__FUNCTION__, status); + result = Core::ERROR_GENERAL; + } + + return result; + } + uint32_t DeleteKey(const ScopeType scope, const string& ns, const string& key) override + { + ASSERT(scope == ScopeType::ACCOUNT); + + uint32_t result; + + grpc::ClientContext context; + + distp::gateway::secure_storage::v1::DeleteValueRequest request; + auto k = new distp::gateway::secure_storage::v1::Key(); + k->set_app_id(ns); + k->set_key(key); + k->set_scope(distp::gateway::secure_storage::v1::Scope::SCOPE_ACCOUNT); + request.set_allocated_key(k); + + distp::gateway::secure_storage::v1::DeleteValueResponse response; + + auto status = (*this)->DeleteValue(&context, request, &response); + if (status.ok()) { + result = Core::ERROR_NONE; + } else { + OnError(__FUNCTION__, status); + result = Core::ERROR_GENERAL; + } + + return result; + } + uint32_t DeleteNamespace(const ScopeType scope, const string& ns) override + { + ASSERT(scope == ScopeType::ACCOUNT); + + uint32_t result; + + grpc::ClientContext context; + + distp::gateway::secure_storage::v1::DeleteAllValuesRequest request; + request.set_app_id(ns); + request.set_scope(distp::gateway::secure_storage::v1::Scope::SCOPE_ACCOUNT); + + distp::gateway::secure_storage::v1::DeleteAllValuesResponse response; + + auto status = (*this)->DeleteAllValues(&context, request, &response); + if (status.ok()) { + result = Core::ERROR_NONE; + } else { + OnError(__FUNCTION__, status); + result = Core::ERROR_GENERAL; + } + + return result; + } + + BEGIN_INTERFACE_MAP(Store2Type) + INTERFACE_ENTRY(Exchange::IStore2) + END_INTERFACE_MAP + + private: + void OnValueChanged(const string& ns, const string& key, const string& value) + { + Core::SafeSyncType lock(_clientLock); + + std::list::iterator + index(_clients.begin()); + + while (index != _clients.end()) { + (*index)->ValueChanged(ScopeType::DEVICE, ns, key, value); + index++; + } + } + void OnError(const char* fn, const grpc::Status& status) const + { + TRACE(Trace::Error, (_T("%s grpc error %d %s %s"), fn, status.error_code(), status.error_message().c_str(), status.error_details().c_str())); + } + + private: + std::list _clients; + Core::CriticalSection _clientLock; + }; + + } // namespace Grpc +} // namespace Plugin +} // namespace WPEFramework diff --git a/PersistentStore/grpc/Stub.h b/PersistentStore/grpc/Stub.h new file mode 100644 index 0000000000..d6ec40fc0c --- /dev/null +++ b/PersistentStore/grpc/Stub.h @@ -0,0 +1,48 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2022 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../Module.h" +#include "secure_storage.grpc.pb.h" + +namespace WPEFramework { +namespace Plugin { + namespace Grpc { + + class Stub { + private: + Stub(const Stub&) = delete; + Stub& operator=(const Stub&) = delete; + + public: + Stub(const std::shared_ptr<::grpc::ChannelInterface>& channel) + : stub_(distp::gateway::secure_storage::v1::SecureStorageService::NewStub(channel)) + { + } + virtual ~Stub() = default; + distp::gateway::secure_storage::v1::SecureStorageService::Stub* operator->() { return (stub_.get()); } + + private: + std::unique_ptr stub_; + }; + + } // namespace Grpc +} // namespace Plugin +} // namespace WPEFramework diff --git a/PersistentStore/grpc/l0test/CMakeLists.txt b/PersistentStore/grpc/l0test/CMakeLists.txt new file mode 100644 index 0000000000..0b7ad8ef2c --- /dev/null +++ b/PersistentStore/grpc/l0test/CMakeLists.txt @@ -0,0 +1,107 @@ +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.14) + +project(grpcl0test) + +set(CMAKE_CXX_STANDARD 11) + +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip +) +FetchContent_MakeAvailable(googletest) + +find_package(WPEFramework) +find_package(${NAMESPACE}Plugins REQUIRED) + +add_executable(${PROJECT_NAME} + ../../Module.cpp + StubTest.cpp + Store2Test.cpp +) + +find_package(PkgConfig REQUIRED) +pkg_search_module(gRPC REQUIRED grpc) +find_package(Protobuf REQUIRED) +find_program(GRPC_CPP_PLUGIN grpc_cpp_plugin REQUIRED) + +FetchContent_Declare( + validate + GIT_REPOSITORY https://github.com/bufbuild/protoc-gen-validate.git + GIT_TAG v1.0.4 +) +FetchContent_MakeAvailable(validate) +protobuf_generate( + OUT_VAR VALIDATE_OUT + PROTOS ${validate_SOURCE_DIR}/validate/validate.proto + PROTOC_OUT_DIR ${validate_SOURCE_DIR} + IMPORT_DIRS ${validate_SOURCE_DIR} +) +add_custom_target(validate_out DEPENDS ${VALIDATE_OUT}) +add_dependencies(${PROJECT_NAME} validate_out) +target_sources(${PROJECT_NAME} PRIVATE ${validate_SOURCE_DIR}/validate/validate.pb.cc) +target_include_directories(${PROJECT_NAME} PRIVATE ${validate_SOURCE_DIR}) +set_property(SOURCE ${validate_SOURCE_DIR}/validate/validate.pb.cc PROPERTY GENERATED 1) + +FetchContent_Declare( + googleapis + GIT_REPOSITORY https://github.com/googleapis/googleapis.git + GIT_TAG master +) +FetchContent_MakeAvailable(googleapis) +protobuf_generate( + OUT_VAR GOOGLEAPIS_OUT + PROTOS ${googleapis_SOURCE_DIR}/google/api/field_behavior.proto + PROTOC_OUT_DIR ${googleapis_SOURCE_DIR} + IMPORT_DIRS ${googleapis_SOURCE_DIR} +) +add_custom_target(googleapis_out DEPENDS ${GOOGLEAPIS_OUT}) +add_dependencies(${PROJECT_NAME} googleapis_out) +target_sources(${PROJECT_NAME} PRIVATE ${googleapis_SOURCE_DIR}/google/api/field_behavior.pb.cc) +target_include_directories(${PROJECT_NAME} PRIVATE ${googleapis_SOURCE_DIR}) +set_property(SOURCE ${googleapis_SOURCE_DIR}/google/api/field_behavior.pb.cc PROPERTY GENERATED 1) + +protobuf_generate( + PROTOS ../secure_storage.proto + TARGET ${PROJECT_NAME} + IMPORT_DIRS ${validate_SOURCE_DIR} ${googleapis_SOURCE_DIR} + APPEND_PATH +) +protobuf_generate( + PROTOS ../secure_storage.proto + TARGET ${PROJECT_NAME} + LANGUAGE grpc + PLUGIN protoc-gen-grpc=${GRPC_CPP_PLUGIN} + IMPORT_DIRS ${validate_SOURCE_DIR} ${googleapis_SOURCE_DIR} + APPEND_PATH + GENERATE_EXTENSIONS .grpc.pb.h .grpc.pb.cc +) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +target_link_options(${PROJECT_NAME} PRIVATE "LINKER:--copy-dt-needed-entries") + +target_link_libraries(${PROJECT_NAME} + gmock_main + ${NAMESPACE}Plugins::${NAMESPACE}Plugins + grpc++ + ${Protobuf_LIBRARIES} +) + +install(TARGETS ${PROJECT_NAME} DESTINATION bin) diff --git a/PersistentStore/grpc/l0test/SecureStorageServiceMock.h b/PersistentStore/grpc/l0test/SecureStorageServiceMock.h new file mode 100644 index 0000000000..3165e289eb --- /dev/null +++ b/PersistentStore/grpc/l0test/SecureStorageServiceMock.h @@ -0,0 +1,14 @@ +#pragma once + +#include "secure_storage.grpc.pb.h" +#include + +class SecureStorageServiceMock : public ::distp::gateway::secure_storage::v1::SecureStorageService::Service { +public: + ~SecureStorageServiceMock() override = default; + MOCK_METHOD(::grpc::Status, GetValue, (::grpc::ServerContext * context, const ::distp::gateway::secure_storage::v1::GetValueRequest* request, ::distp::gateway::secure_storage::v1::GetValueResponse* response), (override)); + MOCK_METHOD(::grpc::Status, UpdateValue, (::grpc::ServerContext * context, const ::distp::gateway::secure_storage::v1::UpdateValueRequest* request, ::distp::gateway::secure_storage::v1::UpdateValueResponse* response), (override)); + MOCK_METHOD(::grpc::Status, DeleteValue, (::grpc::ServerContext * context, const ::distp::gateway::secure_storage::v1::DeleteValueRequest* request, ::distp::gateway::secure_storage::v1::DeleteValueResponse* response), (override)); + MOCK_METHOD(::grpc::Status, DeleteAllValues, (::grpc::ServerContext * context, const ::distp::gateway::secure_storage::v1::DeleteAllValuesRequest* request, ::distp::gateway::secure_storage::v1::DeleteAllValuesResponse* response), (override)); + MOCK_METHOD(::grpc::Status, SeedValue, (::grpc::ServerContext * context, const ::distp::gateway::secure_storage::v1::SeedValueRequest* request, ::distp::gateway::secure_storage::v1::SeedValueResponse* response), (override)); +}; diff --git a/PersistentStore/grpc/l0test/Server.h b/PersistentStore/grpc/l0test/Server.h new file mode 100644 index 0000000000..b7adb02151 --- /dev/null +++ b/PersistentStore/grpc/l0test/Server.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include + +struct Server { + std::unique_ptr server; + Server(const std::string& uri, grpc::Service* service) + { + grpc::ServerBuilder builder; + builder.AddListeningPort(uri, grpc::InsecureServerCredentials()); + builder.RegisterService(service); + server = builder.BuildAndStart(); + } + ~Server() + { + server->Shutdown(); + } +}; diff --git a/PersistentStore/grpc/l0test/Store2Test.cpp b/PersistentStore/grpc/l0test/Store2Test.cpp new file mode 100644 index 0000000000..dfba9c1946 --- /dev/null +++ b/PersistentStore/grpc/l0test/Store2Test.cpp @@ -0,0 +1,183 @@ +#include +#include + +#include + +#include "../Store2Type.h" +#include "../Stub.h" +#include "SecureStorageServiceMock.h" +#include "Server.h" + +using ::testing::_; +using ::testing::Eq; +using ::testing::Gt; +using ::testing::Invoke; +using ::testing::IsFalse; +using ::testing::IsTrue; +using ::testing::Le; +using ::testing::NiceMock; +using ::testing::Test; + +using WPEFramework::Exchange::IStore2; +using WPEFramework::Plugin::Grpc::Store2Type; +using WPEFramework::Plugin::Grpc::Stub; + +using ::distp::gateway::secure_storage::v1::DeleteAllValuesRequest; +using ::distp::gateway::secure_storage::v1::DeleteAllValuesResponse; +using ::distp::gateway::secure_storage::v1::DeleteValueRequest; +using ::distp::gateway::secure_storage::v1::DeleteValueResponse; +using ::distp::gateway::secure_storage::v1::GetValueRequest; +using ::distp::gateway::secure_storage::v1::GetValueResponse; +using ::distp::gateway::secure_storage::v1::Key; +using ::distp::gateway::secure_storage::v1::Scope; +using ::distp::gateway::secure_storage::v1::UpdateValueRequest; +using ::distp::gateway::secure_storage::v1::UpdateValueResponse; +using ::distp::gateway::secure_storage::v1::Value; + +const auto URI = "0.0.0.0:50051"; + +class AStore2 : public Test { +protected: + NiceMock service; + Server server; + WPEFramework::Core::ProxyType store2; + AStore2() + : server(URI, &service) + , store2(WPEFramework::Core::ProxyType>::Create( + grpc::CreateChannel(URI, grpc::InsecureChannelCredentials()))) + { + } +}; + +TEST_F(AStore2, GetsValueWithTtl) +{ + const auto value = "value_1"; + const auto key = "key_1"; + const auto app_id = "app_id_1"; + const auto ttl = 100; + GetValueRequest req; + ON_CALL(service, GetValue(_, _, _)) + .WillByDefault(Invoke( + [&](::grpc::ServerContext*, const GetValueRequest* request, GetValueResponse* response) { + req = (*request); + auto v = new Value(); + v->set_value(value); + auto t = new google::protobuf::Duration(); + t->set_seconds(ttl); + v->set_allocated_ttl(t); + auto k = new Key(); + k->set_key(request->key().key()); + k->set_app_id(request->key().app_id()); + k->set_scope(request->key().scope()); + v->set_allocated_key(k); + response->set_allocated_value(v); + return grpc::Status::OK; + })); + + string v; + uint32_t t; + ASSERT_THAT(store2->GetValue(IStore2::ScopeType::ACCOUNT, app_id, key, v, t), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(req.has_key(), IsTrue()); + EXPECT_THAT(req.key().key(), Eq(key)); + EXPECT_THAT(req.key().app_id(), Eq(app_id)); + EXPECT_THAT(req.key().scope(), Eq(Scope::SCOPE_ACCOUNT)); + EXPECT_THAT(v, Eq(value)); + EXPECT_THAT(t, Eq(ttl)); +} + +TEST_F(AStore2, GetsValueWithExpireTime) +{ + const auto value = "value_1"; + const auto key = "key_1"; + const auto app_id = "app_id_1"; + const auto ttl = 100; + GetValueRequest req; + ON_CALL(service, GetValue(_, _, _)) + .WillByDefault(Invoke( + [&](::grpc::ServerContext*, const GetValueRequest* request, GetValueResponse* response) { + req = (*request); + auto v = new Value(); + v->set_value(value); + auto t = new google::protobuf::Timestamp(); + t->set_seconds(ttl + (WPEFramework::Core::Time::Now().Ticks() / WPEFramework::Core::Time::TicksPerMillisecond / 1000)); + v->set_allocated_expire_time(t); + auto k = new Key(); + k->set_key(request->key().key()); + k->set_app_id(request->key().app_id()); + k->set_scope(request->key().scope()); + v->set_allocated_key(k); + response->set_allocated_value(v); + return grpc::Status::OK; + })); + + string v; + uint32_t t; + ASSERT_THAT(store2->GetValue(IStore2::ScopeType::ACCOUNT, app_id, key, v, t), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(req.has_key(), IsTrue()); + EXPECT_THAT(req.key().key(), Eq(key)); + EXPECT_THAT(req.key().app_id(), Eq(app_id)); + EXPECT_THAT(req.key().scope(), Eq(Scope::SCOPE_ACCOUNT)); + EXPECT_THAT(v, Eq(value)); + EXPECT_THAT(t, Le(ttl)); + EXPECT_THAT(t, Gt(0)); +} + +TEST_F(AStore2, SetsValueWithTtl) +{ + const auto value = "value_1"; + const auto key = "key_1"; + const auto app_id = "app_id_1"; + const uint32_t ttl = 100; + UpdateValueRequest req; + ON_CALL(service, UpdateValue(_, _, _)) + .WillByDefault(Invoke( + [&](::grpc::ServerContext*, const UpdateValueRequest* request, UpdateValueResponse*) { + req = (*request); + return grpc::Status::OK; + })); + + ASSERT_THAT(store2->SetValue(IStore2::ScopeType::ACCOUNT, app_id, key, value, ttl), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(req.has_value(), IsTrue()); + EXPECT_THAT(req.value().value(), Eq(value)); + ASSERT_THAT(req.value().has_key(), IsTrue()); + EXPECT_THAT(req.value().key().key(), Eq(key)); + EXPECT_THAT(req.value().key().app_id(), Eq(app_id)); + EXPECT_THAT(req.value().key().scope(), Eq(Scope::SCOPE_ACCOUNT)); + ASSERT_THAT(req.value().has_ttl(), IsTrue()); + EXPECT_THAT(req.value().ttl().seconds(), Eq(ttl)); +} + +TEST_F(AStore2, DeletesKey) +{ + const auto key = "key_1"; + const auto app_id = "app_id_1"; + DeleteValueRequest req; + ON_CALL(service, DeleteValue(_, _, _)) + .WillByDefault(Invoke( + [&](::grpc::ServerContext*, const DeleteValueRequest* request, DeleteValueResponse*) { + req = (*request); + return grpc::Status::OK; + })); + + ASSERT_THAT(store2->DeleteKey(IStore2::ScopeType::ACCOUNT, app_id, key), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(req.has_key(), IsTrue()); + EXPECT_THAT(req.key().key(), Eq(key)); + EXPECT_THAT(req.key().app_id(), Eq(app_id)); + EXPECT_THAT(req.key().scope(), Eq(Scope::SCOPE_ACCOUNT)); +} + +TEST_F(AStore2, DeletesNamespace) +{ + const auto app_id = "app_id_1"; + DeleteAllValuesRequest req; + ON_CALL(service, DeleteAllValues(_, _, _)) + .WillByDefault(Invoke( + [&](::grpc::ServerContext*, const DeleteAllValuesRequest* request, DeleteAllValuesResponse*) { + req = (*request); + return grpc::Status::OK; + })); + + ASSERT_THAT(store2->DeleteNamespace(IStore2::ScopeType::ACCOUNT, app_id), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(req.app_id(), Eq(app_id)); + EXPECT_THAT(req.scope(), Eq(Scope::SCOPE_ACCOUNT)); +} diff --git a/PersistentStore/grpc/l0test/StubTest.cpp b/PersistentStore/grpc/l0test/StubTest.cpp new file mode 100644 index 0000000000..820bd2c432 --- /dev/null +++ b/PersistentStore/grpc/l0test/StubTest.cpp @@ -0,0 +1,277 @@ +#include +#include + +#include + +#include "../Stub.h" +#include "SecureStorageServiceMock.h" +#include "Server.h" + +using ::testing::_; +using ::testing::Eq; +using ::testing::Invoke; +using ::testing::IsTrue; +using ::testing::NiceMock; +using ::testing::Test; + +using WPEFramework::Plugin::Grpc::Stub; + +using ::distp::gateway::secure_storage::v1::DeleteAllValuesRequest; +using ::distp::gateway::secure_storage::v1::DeleteAllValuesResponse; +using ::distp::gateway::secure_storage::v1::DeleteValueRequest; +using ::distp::gateway::secure_storage::v1::DeleteValueResponse; +using ::distp::gateway::secure_storage::v1::GetValueRequest; +using ::distp::gateway::secure_storage::v1::GetValueResponse; +using ::distp::gateway::secure_storage::v1::Key; +using ::distp::gateway::secure_storage::v1::Scope; +using ::distp::gateway::secure_storage::v1::UpdateValueRequest; +using ::distp::gateway::secure_storage::v1::UpdateValueResponse; +using ::distp::gateway::secure_storage::v1::Value; + +const auto URI = "0.0.0.0:50051"; + +class AStub : public Test { +protected: + NiceMock service; + Server server; + Stub stub; + AStub() + : server(URI, &service) + , stub(grpc::CreateChannel(URI, grpc::InsecureChannelCredentials())) + { + } +}; + +TEST_F(AStub, GetsValueWithTtl) +{ + const auto value = "value_1"; + const auto key = "key_1"; + const auto app_id = "app_id_1"; + const auto scope = Scope::SCOPE_ACCOUNT; + const auto ttl = 100; + GetValueRequest req; + ON_CALL(service, GetValue(_, _, _)) + .WillByDefault(Invoke( + [&](::grpc::ServerContext*, const GetValueRequest* request, GetValueResponse* response) { + req = (*request); + auto v = new Value(); + v->set_value(value); + auto t = new google::protobuf::Duration(); + t->set_seconds(ttl); + v->set_allocated_ttl(t); + auto k = new Key(); + k->set_key(request->key().key()); + k->set_app_id(request->key().app_id()); + k->set_scope(request->key().scope()); + v->set_allocated_key(k); + response->set_allocated_value(v); + return grpc::Status::OK; + })); + + grpc::ClientContext context; + GetValueRequest request; + auto k = new Key(); + k->set_key(key); + k->set_app_id(app_id); + k->set_scope(scope); + request.set_allocated_key(k); + GetValueResponse response; + auto status = stub->GetValue(&context, request, &response); + ASSERT_THAT(status.ok(), IsTrue()); + ASSERT_THAT(req.has_key(), IsTrue()); + EXPECT_THAT(req.key().key(), Eq(key)); + EXPECT_THAT(req.key().app_id(), Eq(app_id)); + EXPECT_THAT(req.key().scope(), Eq(scope)); + ASSERT_THAT(response.has_value(), IsTrue()); + EXPECT_THAT(response.value().value(), Eq(value)); + ASSERT_THAT(response.value().has_key(), IsTrue()); + EXPECT_THAT(response.value().key().key(), Eq(key)); + EXPECT_THAT(response.value().key().app_id(), Eq(app_id)); + EXPECT_THAT(response.value().key().scope(), Eq(scope)); + ASSERT_THAT(response.value().has_ttl(), IsTrue()); + EXPECT_THAT(response.value().ttl().seconds(), Eq(ttl)); +} + +TEST_F(AStub, GetsValueWithExpireTime) +{ + const auto value = "value_1"; + const auto key = "key_1"; + const auto app_id = "app_id_1"; + const auto scope = Scope::SCOPE_ACCOUNT; + const auto expire_time = 1707238543; + GetValueRequest req; + ON_CALL(service, GetValue(_, _, _)) + .WillByDefault(Invoke( + [&](::grpc::ServerContext*, const GetValueRequest* request, GetValueResponse* response) { + req = (*request); + auto v = new Value(); + v->set_value(value); + auto t = new google::protobuf::Timestamp(); + t->set_seconds(expire_time); + v->set_allocated_expire_time(t); + auto k = new Key(); + k->set_key(request->key().key()); + k->set_app_id(request->key().app_id()); + k->set_scope(request->key().scope()); + v->set_allocated_key(k); + response->set_allocated_value(v); + return grpc::Status::OK; + })); + + grpc::ClientContext context; + GetValueRequest request; + auto k = new Key(); + k->set_key(key); + k->set_app_id(app_id); + k->set_scope(scope); + request.set_allocated_key(k); + GetValueResponse response; + auto status = stub->GetValue(&context, request, &response); + ASSERT_THAT(status.ok(), IsTrue()); + ASSERT_THAT(req.has_key(), IsTrue()); + EXPECT_THAT(req.key().key(), Eq(key)); + EXPECT_THAT(req.key().app_id(), Eq(app_id)); + EXPECT_THAT(req.key().scope(), Eq(scope)); + ASSERT_THAT(response.has_value(), IsTrue()); + EXPECT_THAT(response.value().value(), Eq(value)); + ASSERT_THAT(response.value().has_key(), IsTrue()); + EXPECT_THAT(response.value().key().key(), Eq(key)); + EXPECT_THAT(response.value().key().app_id(), Eq(app_id)); + EXPECT_THAT(response.value().key().scope(), Eq(scope)); + ASSERT_THAT(response.value().has_expire_time(), IsTrue()); + EXPECT_THAT(response.value().expire_time().seconds(), Eq(expire_time)); +} + +TEST_F(AStub, UpdatesValueWithTtl) +{ + const auto value = "value_1"; + const auto key = "key_1"; + const auto app_id = "app_id_1"; + const auto scope = Scope::SCOPE_ACCOUNT; + const auto ttl = 100; + UpdateValueRequest req; + ON_CALL(service, UpdateValue(_, _, _)) + .WillByDefault(Invoke( + [&](::grpc::ServerContext*, const UpdateValueRequest* request, UpdateValueResponse*) { + req = (*request); + return grpc::Status::OK; + })); + + grpc::ClientContext context; + UpdateValueRequest request; + auto v = new Value(); + v->set_value(value); + auto t = new google::protobuf::Duration(); + t->set_seconds(ttl); + v->set_allocated_ttl(t); + auto k = new Key(); + k->set_key(key); + k->set_app_id(app_id); + k->set_scope(scope); + v->set_allocated_key(k); + request.set_allocated_value(v); + UpdateValueResponse response; + auto status = stub->UpdateValue(&context, request, &response); + ASSERT_THAT(status.ok(), IsTrue()); + ASSERT_THAT(req.has_value(), IsTrue()); + EXPECT_THAT(req.value().value(), Eq(value)); + ASSERT_THAT(req.value().has_key(), IsTrue()); + EXPECT_THAT(req.value().key().key(), Eq(key)); + EXPECT_THAT(req.value().key().app_id(), Eq(app_id)); + EXPECT_THAT(req.value().key().scope(), Eq(scope)); + ASSERT_THAT(req.value().has_ttl(), IsTrue()); + EXPECT_THAT(req.value().ttl().seconds(), Eq(ttl)); +} + +TEST_F(AStub, UpdatesValueWithExpireTime) +{ + const auto value = "value_1"; + const auto key = "key_1"; + const auto app_id = "app_id_1"; + const auto scope = Scope::SCOPE_ACCOUNT; + const auto expire_time = 1707238543; + UpdateValueRequest req; + ON_CALL(service, UpdateValue(_, _, _)) + .WillByDefault(Invoke( + [&](::grpc::ServerContext*, const UpdateValueRequest* request, UpdateValueResponse*) { + req = (*request); + return grpc::Status::OK; + })); + + grpc::ClientContext context; + UpdateValueRequest request; + auto v = new Value(); + v->set_value(value); + auto t = new google::protobuf::Timestamp(); + t->set_seconds(expire_time); + v->set_allocated_expire_time(t); + auto k = new Key(); + k->set_key(key); + k->set_app_id(app_id); + k->set_scope(scope); + v->set_allocated_key(k); + request.set_allocated_value(v); + UpdateValueResponse response; + auto status = stub->UpdateValue(&context, request, &response); + ASSERT_THAT(status.ok(), IsTrue()); + ASSERT_THAT(req.has_value(), IsTrue()); + EXPECT_THAT(req.value().value(), Eq(value)); + ASSERT_THAT(req.value().has_key(), IsTrue()); + EXPECT_THAT(req.value().key().key(), Eq(key)); + EXPECT_THAT(req.value().key().app_id(), Eq(app_id)); + EXPECT_THAT(req.value().key().scope(), Eq(scope)); + ASSERT_THAT(req.value().has_expire_time(), IsTrue()); + EXPECT_THAT(req.value().expire_time().seconds(), Eq(expire_time)); +} + +TEST_F(AStub, DeletesValue) +{ + const auto key = "key_1"; + const auto app_id = "app_id_1"; + const auto scope = Scope::SCOPE_ACCOUNT; + DeleteValueRequest req; + ON_CALL(service, DeleteValue(_, _, _)) + .WillByDefault(Invoke( + [&](::grpc::ServerContext*, const DeleteValueRequest* request, DeleteValueResponse*) { + req = (*request); + return grpc::Status::OK; + })); + + grpc::ClientContext context; + DeleteValueRequest request; + auto k = new Key(); + k->set_key(key); + k->set_app_id(app_id); + k->set_scope(scope); + request.set_allocated_key(k); + DeleteValueResponse response; + auto status = stub->DeleteValue(&context, request, &response); + ASSERT_THAT(status.ok(), IsTrue()); + ASSERT_THAT(req.has_key(), IsTrue()); + EXPECT_THAT(req.key().key(), Eq(key)); + EXPECT_THAT(req.key().app_id(), Eq(app_id)); + EXPECT_THAT(req.key().scope(), Eq(scope)); +} + +TEST_F(AStub, DeletesAllValues) +{ + const auto app_id = "app_id_1"; + const auto scope = Scope::SCOPE_ACCOUNT; + DeleteAllValuesRequest req; + ON_CALL(service, DeleteAllValues(_, _, _)) + .WillByDefault(Invoke( + [&](::grpc::ServerContext*, const DeleteAllValuesRequest* request, DeleteAllValuesResponse*) { + req = (*request); + return grpc::Status::OK; + })); + + grpc::ClientContext context; + DeleteAllValuesRequest request; + request.set_app_id(app_id); + request.set_scope(scope); + DeleteAllValuesResponse response; + auto status = stub->DeleteAllValues(&context, request, &response); + ASSERT_THAT(status.ok(), IsTrue()); + ASSERT_THAT(req.app_id(), Eq(app_id)); + EXPECT_THAT(req.scope(), Eq(scope)); +} diff --git a/PersistentStore/grpc/secure_storage.proto b/PersistentStore/grpc/secure_storage.proto new file mode 100644 index 0000000000..d38eb1880d --- /dev/null +++ b/PersistentStore/grpc/secure_storage.proto @@ -0,0 +1,136 @@ +syntax = "proto3"; + +package distp.gateway.secure_storage.v1; + +import "google/api/field_behavior.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "validate/validate.proto"; + +// SecureStorageService handles the storage and deletion of data (represented as string values) from applications given a particular key and scope. +service SecureStorageService { + // GetValue retrieves the value string stored in SecureStorage based on the scope and key provided for the current application. + rpc GetValue(GetValueRequest) returns (GetValueResponse); + // UpdateValue stores the string value provided in SecureStorage against the scope and key provided for the current application. + rpc UpdateValue(UpdateValueRequest) returns (UpdateValueResponse); + // DeleteValue removes the string value stored in SecureStorage, if present, defined by the scope and key provided for the current application/device. + rpc DeleteValue(DeleteValueRequest) returns (DeleteValueResponse); + // DeleteAllValues removes all values stored against the provided application for the given account. This includes all key/value pairs + // stored against the device scope for other devices within the account. Please note that this method does not take a key as input. + // Also, DeleteAllValues is a separate method given that access to delete all values may be controlled by explicit capabilities. + rpc DeleteAllValues(DeleteAllValuesRequest) returns (DeleteAllValuesResponse); + // SeedValue stores the string value provided in SecureStorage against the scope and key provided for the current application. + // This is a management API so the stored value will not cause the distributor's storage limits to be exceeded. + rpc SeedValue(SeedValueRequest) returns (SeedValueResponse); +} + +// Key is a group of fields that contribute to building the composite key that will be used to store the data in SecureStorage. +message Key { + // key of the key,value pair to be retrieved from SecureStorage. + string key = 1 [ + (google.api.field_behavior) = REQUIRED, + (validate.rules).string.min_len = 1 + ]; + // Scope describes the extent of the key,value pair. Scope is determined by the distributor. + Scope scope = 2 [ + (google.api.field_behavior) = REQUIRED, + (validate.rules).enum.defined_only = true, + (validate.rules).enum.not_in = 0 + ]; + + // app_id is the unique identifier of an app or family of apps. + string app_id = 3; +} + +// Value contains the value of the requested data as well as some other relevant fields like scope and expiry useful for the client. +message Value { + // key of the key,value pair that was retrieved from SecureStorage. + Key key = 1; + // value is the value associated with the key,value pair retrieved from SecureStorage. + string value = 2; + // expiration returns the expire time of the retrieved value. Conforms to AIP-214. + oneof expiration { + // Timestamp in UTC of when this resource is considered expired. + // This is *always* provided on output, regardless of what was sent on input. + google.protobuf.Timestamp expire_time = 3; + + // Input only. The TTL for this resource. + google.protobuf.Duration ttl = 4 [(google.api.field_behavior) = INPUT_ONLY]; + } +} + +// GetValueRequest is the request to retrieve the SecureStorage data. +message GetValueRequest { + // key is the group of fields that contribute to building the composite key that will be used to store the data in SecureStorage. + Key key = 1 [ + (google.api.field_behavior) = REQUIRED, + (validate.rules).message.required = true + ]; +} + +// GetValueResponse is the response containing the value of the requested key within the specified scope. +message GetValueResponse { + // value contains the data associated with the key,value pair that was stored in SecureStorage. + Value value = 1; +} + +// UpdateValueRequest is the request to store data in SecureStorage. +message UpdateValueRequest { + // key is the group of fields that contribute to building the composite key that will be used to store the data in SecureStorage. + Value value = 1 [ + (google.api.field_behavior) = REQUIRED, + (validate.rules).message.required = true + ]; +} + +// UpdateValueResponse is the response from the GetValue method. +message UpdateValueResponse {} + +// DeleteValueRequest is the request to remove a stored value given the key and scope. +message DeleteValueRequest { + // key is the group of fields that contribute to building the composite key that will be used to store the data in SecureStorage. + Key key = 1 [ + (google.api.field_behavior) = REQUIRED, + (validate.rules).message.required = true + ]; +} + +// DeleteValueResponse is the response from the DeleteValue method. +message DeleteValueResponse {} + +// DeleteAllValuesRequest is the request to delete all of the keys associated with an app under the given account. +message DeleteAllValuesRequest { + // app_id is the unique identifier of an app or family of apps. + string app_id = 1 [(google.api.field_behavior) = OPTIONAL]; + // Scope describes the extent of the key,value pair. Scope is determined by the distributor. + Scope scope = 2 [ + (google.api.field_behavior) = REQUIRED, + (validate.rules).enum.defined_only = true, + (validate.rules).enum.not_in = 0 + ]; +} + +// DeleteAllValuesResponse is the response from the DeleteAllValues method. +message DeleteAllValuesResponse {} + +// SeedValueRequest is the request to store data in SecureStorage. Stored data will not cause the distributor's storage limits to be exceeded. +message SeedValueRequest { + // value contains the data associated with the key,value pair that will be stored in SecureStorage. + Value value = 1 [ + (google.api.field_behavior) = REQUIRED, + (validate.rules).message.required = true + ]; +} + +// SeedValueResponse is the response from the SeedValue method. +message SeedValueResponse {} + +// Enumerated values for scope. +enum Scope { + // Represents an unset or invalid scope. + SCOPE_UNSPECIFIED = 0; + // Account scope. + SCOPE_ACCOUNT = 1; + // Device scope. + SCOPE_DEVICE = 2; +} diff --git a/PersistentStore/l1test/CMakeLists.txt b/PersistentStore/l0test/CMakeLists.txt similarity index 76% rename from PersistentStore/l1test/CMakeLists.txt rename to PersistentStore/l0test/CMakeLists.txt index 4aa60125d4..90386b7073 100644 --- a/PersistentStore/l1test/CMakeLists.txt +++ b/PersistentStore/l0test/CMakeLists.txt @@ -15,9 +15,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -project(persistentstorel1test) +cmake_minimum_required(VERSION 3.14) -cmake_minimum_required(VERSION 3.11) +project(persistentstorel0test) set(CMAKE_CXX_STANDARD 11) @@ -32,29 +32,18 @@ find_package(WPEFramework) find_package(${NAMESPACE}Plugins REQUIRED) find_package(${NAMESPACE}Definitions REQUIRED) -find_package(PkgConfig) -pkg_search_module(SQLITE REQUIRED sqlite3) - add_executable(${PROJECT_NAME} ../Module.cpp - ../sqlite/Handle.cpp ../PersistentStore.cpp ../PersistentStoreJsonRpc.cpp - JsonRpcTest.cpp - PluginTest.cpp - StoreTest.cpp - ) - -add_definitions(${SQLITE_CFLAGS_OTHER}) -link_directories(${SQLITE_LIBRARY_DIRS}) - -target_include_directories(${PROJECT_NAME} PRIVATE ../) + PersistentStoreJsonRpcTest.cpp + PersistentStoreStoreTest.cpp +) target_link_libraries(${PROJECT_NAME} PRIVATE gmock_main ${NAMESPACE}Plugins::${NAMESPACE}Plugins ${NAMESPACE}Definitions::${NAMESPACE}Definitions - ${SQLITE_LIBRARIES} - ) +) install(TARGETS ${PROJECT_NAME} DESTINATION bin) diff --git a/PersistentStore/l0test/PersistentStoreJsonRpcTest.cpp b/PersistentStore/l0test/PersistentStoreJsonRpcTest.cpp new file mode 100644 index 0000000000..30912ebd3e --- /dev/null +++ b/PersistentStore/l0test/PersistentStoreJsonRpcTest.cpp @@ -0,0 +1,492 @@ +#include +#include + +#include "../PersistentStore.h" +#include "ServiceMock.h" +#include "Store2Mock.h" +#include "StoreCacheMock.h" +#include "StoreInspectorMock.h" +#include "StoreLimitMock.h" + +using ::testing::_; +using ::testing::Eq; +using ::testing::Invoke; +using ::testing::IsFalse; +using ::testing::IsTrue; +using ::testing::NiceMock; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::Test; +using ::WPEFramework::Core::PublishedServiceType; +using ::WPEFramework::Exchange::IStore2; +using ::WPEFramework::Exchange::IStoreInspector; +using ::WPEFramework::Exchange::IStoreLimit; +using ::WPEFramework::JsonData::PersistentStore::DeleteKeyParamsInfo; +using ::WPEFramework::JsonData::PersistentStore::DeleteNamespaceParamsInfo; +using ::WPEFramework::JsonData::PersistentStore::GetKeysResultData; +using ::WPEFramework::JsonData::PersistentStore::GetNamespacesResultData; +using ::WPEFramework::JsonData::PersistentStore::GetNamespaceStorageLimitResultData; +using ::WPEFramework::JsonData::PersistentStore::GetStorageSizesResultData; +using ::WPEFramework::JsonData::PersistentStore::GetValueResultData; +using ::WPEFramework::JsonData::PersistentStore::SetNamespaceStorageLimitParamsData; +using ::WPEFramework::JsonData::PersistentStore::SetValueParamsData; +using ::WPEFramework::Plugin::PersistentStore; +using ::WPEFramework::PluginHost::ILocalDispatcher; +using ::WPEFramework::PluginHost::IPlugin; +using ::WPEFramework::PluginHost::IShell; +using ::WPEFramework::RPC::IStringIterator; +using ::WPEFramework::RPC::IteratorType; +using ::WPEFramework::RPC::StringIterator; + +const auto kValue = "value_1"; +const auto kKey = "key_1"; +const auto kAppId = "app_id_1"; +const auto kTtl = 100; +const std::vector kKeys{ "key_1", "key_2" }; +const std::vector kAppIds{ "app_id_1", "app_id_2" }; +const std::vector kSizes{ { "app_id_1", 10 }, { "app_id_2", 20 } }; +const auto kSize = 1000; + +class APersistentStoreJsonRpc : public Test { +protected: + IShell* service; + IPlugin* plugin; + ILocalDispatcher* jsonRpc; + APersistentStoreJsonRpc() + : service(WPEFramework::Core::Service>::Create()) + , plugin(WPEFramework::Core::Service::Create()) + , jsonRpc(plugin->QueryInterface()) + { + } + ~APersistentStoreJsonRpc() + { + if (jsonRpc) + jsonRpc->Release(); + if (plugin) + plugin->Release(); + if (service) + service->Release(); + } + void SetUp() override + { + ASSERT_THAT(service, NotNull()); + ASSERT_THAT(plugin, NotNull()); + ASSERT_THAT(jsonRpc, NotNull()); + } +}; + +TEST_F(APersistentStoreJsonRpc, GetsValueInDeviceScope) +{ + class DeviceStore2 : public NiceMock { + public: + DeviceStore2() + { + EXPECT_CALL(*this, GetValue(_, _, _, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, const string& key, string& value, uint32_t& ttl) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + EXPECT_THAT(ns, Eq(kAppId)); + EXPECT_THAT(key, Eq(kKey)); + value = kValue; + ttl = kTtl; + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + DeleteKeyParamsInfo params; + params.Namespace = kAppId; + params.Key = kKey; + string paramsJsonStr; + params.ToString(paramsJsonStr); + string resultJsonStr; + ASSERT_THAT(jsonRpc->Invoke(0, 0, "", "getValue", paramsJsonStr, resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + GetValueResultData result; + result.FromString(resultJsonStr); + EXPECT_THAT(result.Value.Value(), Eq(kValue)); + EXPECT_THAT(result.Ttl.Value(), Eq(kTtl)); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStoreJsonRpc, GetsValueInAccountScope) +{ + class AccountStore2 : public NiceMock { + public: + AccountStore2() + { + EXPECT_CALL(*this, GetValue(_, _, _, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, const string& key, string& value, uint32_t& ttl) { + EXPECT_THAT(scope, Eq(ScopeType::ACCOUNT)); + EXPECT_THAT(ns, Eq(kAppId)); + EXPECT_THAT(key, Eq(kKey)); + value = kValue; + ttl = kTtl; + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + DeleteKeyParamsInfo params; + params.Scope = WPEFramework::JsonData::PersistentStore::ScopeType::ACCOUNT; + params.Namespace = kAppId; + params.Key = kKey; + string paramsJsonStr; + params.ToString(paramsJsonStr); + string resultJsonStr; + ASSERT_THAT(jsonRpc->Invoke(0, 0, "", "getValue", paramsJsonStr, resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + GetValueResultData result; + result.FromString(resultJsonStr); + EXPECT_THAT(result.Value.Value(), Eq(kValue)); + EXPECT_THAT(result.Ttl.Value(), Eq(kTtl)); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStoreJsonRpc, SetsValueInDeviceScope) +{ + class DeviceStore2 : public NiceMock { + public: + DeviceStore2() + { + EXPECT_CALL(*this, SetValue(_, _, _, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, const string& key, const string& value, const uint32_t ttl) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + EXPECT_THAT(ns, Eq(kAppId)); + EXPECT_THAT(key, Eq(kKey)); + EXPECT_THAT(value, Eq(kValue)); + EXPECT_THAT(ttl, Eq(kTtl)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + SetValueParamsData params; + params.Namespace = kAppId; + params.Key = kKey; + params.Value = kValue; + params.Ttl = kTtl; + string paramsJsonStr; + params.ToString(paramsJsonStr); + string resultJsonStr; + EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "setValue", paramsJsonStr, resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStoreJsonRpc, SetsValueInAccountScope) +{ + class AccountStore2 : public NiceMock { + public: + AccountStore2() + { + EXPECT_CALL(*this, SetValue(_, _, _, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, const string& key, const string& value, const uint32_t ttl) { + EXPECT_THAT(scope, Eq(ScopeType::ACCOUNT)); + EXPECT_THAT(ns, Eq(kAppId)); + EXPECT_THAT(key, Eq(kKey)); + EXPECT_THAT(value, Eq(kValue)); + EXPECT_THAT(ttl, Eq(kTtl)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + SetValueParamsData params; + params.Scope = WPEFramework::JsonData::PersistentStore::ScopeType::ACCOUNT; + params.Namespace = kAppId; + params.Key = kKey; + params.Value = kValue; + params.Ttl = kTtl; + string paramsJsonStr; + params.ToString(paramsJsonStr); + string resultJsonStr; + EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "setValue", paramsJsonStr, resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStoreJsonRpc, DeletesKeyInDeviceScope) +{ + class DeviceStore2 : public NiceMock { + public: + DeviceStore2() + { + EXPECT_CALL(*this, DeleteKey(_, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, const string& key) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + EXPECT_THAT(ns, Eq(kAppId)); + EXPECT_THAT(key, Eq(kKey)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + DeleteKeyParamsInfo params; + params.Namespace = kAppId; + params.Key = kKey; + string paramsJsonStr; + params.ToString(paramsJsonStr); + string resultJsonStr; + EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "deleteKey", paramsJsonStr, resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStoreJsonRpc, DeletesKeyInAccountScope) +{ + class AccountStore2 : public NiceMock { + public: + AccountStore2() + { + EXPECT_CALL(*this, DeleteKey(_, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, const string& key) { + EXPECT_THAT(scope, Eq(ScopeType::ACCOUNT)); + EXPECT_THAT(ns, Eq(kAppId)); + EXPECT_THAT(key, Eq(kKey)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + DeleteKeyParamsInfo params; + params.Scope = WPEFramework::JsonData::PersistentStore::ScopeType::ACCOUNT; + params.Namespace = kAppId; + params.Key = kKey; + string paramsJsonStr; + params.ToString(paramsJsonStr); + string resultJsonStr; + EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "deleteKey", paramsJsonStr, resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStoreJsonRpc, DeletesNamespaceInDeviceScope) +{ + class DeviceStore2 : public NiceMock { + public: + DeviceStore2() + { + EXPECT_CALL(*this, DeleteNamespace(_, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + EXPECT_THAT(ns, Eq(kAppId)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + DeleteNamespaceParamsInfo params; + params.Namespace = kAppId; + string paramsJsonStr; + params.ToString(paramsJsonStr); + string resultJsonStr; + EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "deleteNamespace", paramsJsonStr, resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStoreJsonRpc, DeletesNamespaceInAccountScope) +{ + class AccountStore2 : public NiceMock { + public: + AccountStore2() + { + EXPECT_CALL(*this, DeleteNamespace(_, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns) { + EXPECT_THAT(scope, Eq(ScopeType::ACCOUNT)); + EXPECT_THAT(ns, Eq(kAppId)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + DeleteNamespaceParamsInfo params; + params.Scope = WPEFramework::JsonData::PersistentStore::ScopeType::ACCOUNT; + params.Namespace = kAppId; + string paramsJsonStr; + params.ToString(paramsJsonStr); + string resultJsonStr; + EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "deleteNamespace", paramsJsonStr, resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStoreJsonRpc, FlushesCache) +{ + class DeviceStoreCache : public NiceMock { + public: + DeviceStoreCache() + { + EXPECT_CALL(*this, FlushCache()) + .WillRepeatedly(Return(WPEFramework::Core::ERROR_NONE)); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + string resultJsonStr; + EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "flushCache", "", resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStoreJsonRpc, GetsKeysInDeviceScope) +{ + class DeviceStoreInspector : public NiceMock { + public: + DeviceStoreInspector() + { + EXPECT_CALL(*this, GetKeys(_, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, IStringIterator*& keys) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + EXPECT_THAT(ns, Eq(kAppId)); + keys = (WPEFramework::Core::Service::Create(kKeys)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + DeleteNamespaceParamsInfo params; + params.Namespace = kAppId; + string paramsJsonStr; + params.ToString(paramsJsonStr); + string resultJsonStr; + ASSERT_THAT(jsonRpc->Invoke(0, 0, "", "getKeys", paramsJsonStr, resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + GetKeysResultData result; + result.FromString(resultJsonStr); + auto index(result.Keys.Elements()); + ASSERT_THAT(index.Next(), IsTrue()); + EXPECT_THAT(index.Current().Value(), Eq(kKeys.at(0))); + ASSERT_THAT(index.Next(), IsTrue()); + EXPECT_THAT(index.Current().Value(), Eq(kKeys.at(1))); + EXPECT_THAT(index.Next(), IsFalse()); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStoreJsonRpc, GetsNamespacesInDeviceScope) +{ + class DeviceStoreInspector : public NiceMock { + public: + DeviceStoreInspector() + { + EXPECT_CALL(*this, GetNamespaces(_, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, IStringIterator*& namespaces) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + namespaces = (WPEFramework::Core::Service::Create(kAppIds)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + string resultJsonStr; + ASSERT_THAT(jsonRpc->Invoke(0, 0, "", "getNamespaces", "", resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + GetNamespacesResultData result; + result.FromString(resultJsonStr); + auto index(result.Namespaces.Elements()); + ASSERT_THAT(index.Next(), IsTrue()); + EXPECT_THAT(index.Current().Value(), Eq(kAppIds.at(0))); + ASSERT_THAT(index.Next(), IsTrue()); + EXPECT_THAT(index.Current().Value(), Eq(kAppIds.at(1))); + EXPECT_THAT(index.Next(), IsFalse()); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStoreJsonRpc, GetsStorageSizesInDeviceScope) +{ + class DeviceStoreInspector : public NiceMock { + public: + DeviceStoreInspector() + { + EXPECT_CALL(*this, GetStorageSizes(_, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, INamespaceSizeIterator*& storageList) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + storageList = (WPEFramework::Core::Service>::Create(kSizes)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + string resultJsonStr; + ASSERT_THAT(jsonRpc->Invoke(0, 0, "", "getStorageSizes", "", resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + GetStorageSizesResultData result; + result.FromString(resultJsonStr); + auto index(result.StorageList.Elements()); + ASSERT_THAT(index.Next(), IsTrue()); + EXPECT_THAT(index.Current().Namespace.Value(), Eq(kSizes.at(0).ns)); + EXPECT_THAT(index.Current().Size.Value(), Eq(kSizes.at(0).size)); + ASSERT_THAT(index.Next(), IsTrue()); + EXPECT_THAT(index.Current().Namespace.Value(), Eq(kSizes.at(1).ns)); + EXPECT_THAT(index.Current().Size.Value(), Eq(kSizes.at(1).size)); + EXPECT_THAT(index.Next(), IsFalse()); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStoreJsonRpc, GetsNamespaceStorageLimitInDeviceScope) +{ + class DeviceStoreLimit : public NiceMock { + public: + DeviceStoreLimit() + { + EXPECT_CALL(*this, GetNamespaceStorageLimit(_, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, uint32_t& size) { + EXPECT_THAT(scope, Eq(IStoreLimit::ScopeType::DEVICE)); + EXPECT_THAT(ns, Eq(kAppId)); + size = kSize; + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + DeleteNamespaceParamsInfo params; + params.Namespace = kAppId; + string paramsJsonStr; + params.ToString(paramsJsonStr); + string resultJsonStr; + ASSERT_THAT(jsonRpc->Invoke(0, 0, "", "getNamespaceStorageLimit", paramsJsonStr, resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + GetNamespaceStorageLimitResultData result; + result.FromString(resultJsonStr); + EXPECT_THAT(result.StorageLimit.Value(), Eq(kSize)); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStoreJsonRpc, SetsNamespaceStorageLimitInDeviceScope) +{ + class DeviceStoreLimit : public NiceMock { + public: + DeviceStoreLimit() + { + EXPECT_CALL(*this, SetNamespaceStorageLimit(_, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, const uint32_t size) { + EXPECT_THAT(scope, Eq(IStoreLimit::ScopeType::DEVICE)); + EXPECT_THAT(ns, Eq(kAppId)); + EXPECT_THAT(size, Eq(kSize)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + SetNamespaceStorageLimitParamsData params; + params.Namespace = kAppId; + params.StorageLimit = kSize; + string paramsJsonStr; + params.ToString(paramsJsonStr); + string resultJsonStr; + EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "setNamespaceStorageLimit", paramsJsonStr, resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + plugin->Deinitialize(service); +} diff --git a/PersistentStore/l0test/PersistentStoreStoreTest.cpp b/PersistentStore/l0test/PersistentStoreStoreTest.cpp new file mode 100644 index 0000000000..ef8fb350a1 --- /dev/null +++ b/PersistentStore/l0test/PersistentStoreStoreTest.cpp @@ -0,0 +1,151 @@ +#include +#include + +#include "../PersistentStore.h" +#include "ServiceMock.h" +#include "Store2Mock.h" + +using ::testing::_; +using ::testing::Eq; +using ::testing::Invoke; +using ::testing::NiceMock; +using ::testing::NotNull; +using ::testing::Test; +using ::WPEFramework::Core::PublishedServiceType; +using ::WPEFramework::Exchange::IStore; +using ::WPEFramework::Exchange::IStore2; +using ::WPEFramework::Plugin::PersistentStore; +using ::WPEFramework::PluginHost::IPlugin; +using ::WPEFramework::PluginHost::IShell; + +const auto kValue = "value_1"; +const auto kKey = "key_1"; +const auto kAppId = "app_id_1"; + +class APersistentStoreStore : public Test { +protected: + IShell* service; + IPlugin* plugin; + APersistentStoreStore() + : service(WPEFramework::Core::Service>::Create()) + , plugin(WPEFramework::Core::Service::Create()) + { + // TODO create IStore in ctor? + } + ~APersistentStoreStore() + { + if (plugin) + plugin->Release(); + if (service) + service->Release(); + } + void SetUp() override + { + ASSERT_THAT(service, NotNull()); + ASSERT_THAT(plugin, NotNull()); + } +}; + +TEST_F(APersistentStoreStore, GetsValueInDeviceScope) +{ + class DeviceStore2 : public NiceMock { + public: + DeviceStore2() + { + EXPECT_CALL(*this, GetValue(_, _, _, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, const string& key, string& value, uint32_t& ttl) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + EXPECT_THAT(ns, Eq(kAppId)); + EXPECT_THAT(key, Eq(kKey)); + value = kValue; + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + auto store = plugin->QueryInterface(); + ASSERT_THAT(store, NotNull()); + string value; + ASSERT_THAT(store->GetValue(kAppId, kKey, value), Eq(WPEFramework::Core::ERROR_NONE)); + EXPECT_THAT(value, Eq(kValue)); + store->Release(); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStoreStore, SetsValueInDeviceScope) +{ + class DeviceStore2 : public NiceMock { + public: + DeviceStore2() + { + EXPECT_CALL(*this, SetValue(_, _, _, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, const string& key, const string& value, const uint32_t ttl) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + EXPECT_THAT(ns, Eq(kAppId)); + EXPECT_THAT(key, Eq(kKey)); + EXPECT_THAT(value, Eq(kValue)); + EXPECT_THAT(ttl, Eq(0)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + auto store = plugin->QueryInterface(); + ASSERT_THAT(store, NotNull()); + EXPECT_THAT(store->SetValue(kAppId, kKey, kValue), Eq(WPEFramework::Core::ERROR_NONE)); + store->Release(); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStoreStore, DeletesKeyInDeviceScope) +{ + class DeviceStore2 : public NiceMock { + public: + DeviceStore2() + { + EXPECT_CALL(*this, DeleteKey(_, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, const string& key) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + EXPECT_THAT(ns, Eq(kAppId)); + EXPECT_THAT(key, Eq(kKey)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + auto store = plugin->QueryInterface(); + ASSERT_THAT(store, NotNull()); + EXPECT_THAT(store->DeleteKey(kAppId, kKey), Eq(WPEFramework::Core::ERROR_NONE)); + store->Release(); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStoreStore, DeletesNamespaceInDeviceScope) +{ + class DeviceStore2 : public NiceMock { + public: + DeviceStore2() + { + EXPECT_CALL(*this, DeleteNamespace(_, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + EXPECT_THAT(ns, Eq(kAppId)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + auto store = plugin->QueryInterface(); + ASSERT_THAT(store, NotNull()); + EXPECT_THAT(store->DeleteNamespace(kAppId), Eq(WPEFramework::Core::ERROR_NONE)); + store->Release(); + plugin->Deinitialize(service); +} diff --git a/PersistentStore/l1test/PluginTest.cpp b/PersistentStore/l0test/PluginTest.cpp similarity index 100% rename from PersistentStore/l1test/PluginTest.cpp rename to PersistentStore/l0test/PluginTest.cpp diff --git a/PersistentStore/l1test/ServiceMock.h b/PersistentStore/l0test/ServiceMock.h similarity index 93% rename from PersistentStore/l1test/ServiceMock.h rename to PersistentStore/l0test/ServiceMock.h index 4c8c01109a..f18d2c9c29 100644 --- a/PersistentStore/l1test/ServiceMock.h +++ b/PersistentStore/l0test/ServiceMock.h @@ -1,15 +1,11 @@ #pragma once -#include - #include "../Module.h" +#include class ServiceMock : public WPEFramework::PluginHost::IShell { public: - virtual ~ServiceMock() override = default; - - MOCK_METHOD(void, AddRef, (), (const, override)); - MOCK_METHOD(uint32_t, Release, (), (const, override)); + ~ServiceMock() override = default; MOCK_METHOD(string, Versions, (), (const, override)); MOCK_METHOD(string, Locator, (), (const, override)); MOCK_METHOD(string, ClassName, (), (const, override)); @@ -27,7 +23,6 @@ class ServiceMock : public WPEFramework::PluginHost::IShell { MOCK_METHOD(WPEFramework::PluginHost::ISubSystem*, SubSystems, (), (override)); MOCK_METHOD(uint32_t, Submit, (const uint32_t, const WPEFramework::Core::ProxyType&), (override)); MOCK_METHOD(void, Notify, (const string&), (override)); - MOCK_METHOD(void*, QueryInterface, (const uint32_t), (override)); MOCK_METHOD(void*, QueryInterfaceByCallsign, (const uint32_t, const string&), (override)); MOCK_METHOD(void, Register, (WPEFramework::PluginHost::IPlugin::INotification*), (override)); MOCK_METHOD(void, Unregister, (WPEFramework::PluginHost::IPlugin::INotification*), (override)); @@ -52,4 +47,7 @@ class ServiceMock : public WPEFramework::PluginHost::IShell { MOCK_METHOD(WPEFramework::Core::hresult, Resumed, (const bool value), (override)); MOCK_METHOD(WPEFramework::Core::hresult, Metadata, (string & info /* @out */), (const, override)); MOCK_METHOD(WPEFramework::Core::hresult, Hibernate, (const uint32_t timeout), (override)); + BEGIN_INTERFACE_MAP(ServiceMock) + INTERFACE_ENTRY(IShell) + END_INTERFACE_MAP }; diff --git a/PersistentStore/l0test/Store2Mock.h b/PersistentStore/l0test/Store2Mock.h new file mode 100644 index 0000000000..35ac62674f --- /dev/null +++ b/PersistentStore/l0test/Store2Mock.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +class Store2Mock : public WPEFramework::Exchange::IStore2 { +public: + ~Store2Mock() override = default; + MOCK_METHOD(uint32_t, Register, (INotification*), (override)); + MOCK_METHOD(uint32_t, Unregister, (INotification*), (override)); + MOCK_METHOD(uint32_t, SetValue, (const ScopeType scope, const string& ns, const string& key, const string& value, const uint32_t ttl), (override)); + MOCK_METHOD(uint32_t, GetValue, (const ScopeType scope, const string& ns, const string& key, string& value, uint32_t& ttl), (override)); + MOCK_METHOD(uint32_t, DeleteKey, (const ScopeType scope, const string& ns, const string& key), (override)); + MOCK_METHOD(uint32_t, DeleteNamespace, (const ScopeType scope, const string& ns), (override)); + BEGIN_INTERFACE_MAP(Store2Mock) + INTERFACE_ENTRY(IStore2) + END_INTERFACE_MAP +}; diff --git a/PersistentStore/l0test/StoreCacheMock.h b/PersistentStore/l0test/StoreCacheMock.h new file mode 100644 index 0000000000..ed7715a013 --- /dev/null +++ b/PersistentStore/l0test/StoreCacheMock.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +class StoreCacheMock : public WPEFramework::Exchange::IStoreCache { +public: + ~StoreCacheMock() override = default; + MOCK_METHOD(uint32_t, FlushCache, (), (override)); + BEGIN_INTERFACE_MAP(StoreCacheMock) + INTERFACE_ENTRY(IStoreCache) + END_INTERFACE_MAP +}; diff --git a/PersistentStore/l0test/StoreInspectorMock.h b/PersistentStore/l0test/StoreInspectorMock.h new file mode 100644 index 0000000000..7765f744a7 --- /dev/null +++ b/PersistentStore/l0test/StoreInspectorMock.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +class StoreInspectorMock : public WPEFramework::Exchange::IStoreInspector { +public: + ~StoreInspectorMock() override = default; + MOCK_METHOD(uint32_t, GetKeys, (const ScopeType scope, const string& ns, IStringIterator*& keys), (override)); + MOCK_METHOD(uint32_t, GetNamespaces, (const ScopeType scope, IStringIterator*& namespaces), (override)); + MOCK_METHOD(uint32_t, GetStorageSizes, (const ScopeType scope, INamespaceSizeIterator*& storageList), (override)); + BEGIN_INTERFACE_MAP(StoreInspectorMock) + INTERFACE_ENTRY(IStoreInspector) + END_INTERFACE_MAP +}; diff --git a/PersistentStore/l0test/StoreLimitMock.h b/PersistentStore/l0test/StoreLimitMock.h new file mode 100644 index 0000000000..2b818984e9 --- /dev/null +++ b/PersistentStore/l0test/StoreLimitMock.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +class StoreLimitMock : public WPEFramework::Exchange::IStoreLimit { +public: + ~StoreLimitMock() override = default; + MOCK_METHOD(uint32_t, GetNamespaceStorageLimit, (const ScopeType scope, const string& ns, uint32_t& size), (override)); + MOCK_METHOD(uint32_t, SetNamespaceStorageLimit, (const ScopeType scope, const string& ns, const uint32_t size), (override)); + BEGIN_INTERFACE_MAP(StoreLimitMock) + INTERFACE_ENTRY(IStoreLimit) + END_INTERFACE_MAP +}; diff --git a/PersistentStore/l1test/JsonRpcTest.cpp b/PersistentStore/l1test/JsonRpcTest.cpp deleted file mode 100644 index c090fc31c2..0000000000 --- a/PersistentStore/l1test/JsonRpcTest.cpp +++ /dev/null @@ -1,265 +0,0 @@ -#include -#include - -#include "../PersistentStore.h" - -#include "ServiceMock.h" - -using namespace WPEFramework; -using namespace WPEFramework::Plugin; - -using ::testing::Eq; -using ::testing::IsFalse; -using ::testing::IsTrue; -using ::testing::NiceMock; -using ::testing::NotNull; -using ::testing::Test; - -const std::string Path = "/tmp/persistentstore/l1test/jsonrpctest"; -const uint32_t MaxSize = 100; -const uint32_t MaxValue = 10; -const uint32_t Limit = 50; - -class AJsonRpc : public Test { -protected: - NiceMock service; - PluginHost::IPlugin* plugin; - PluginHost::ILocalDispatcher* jsonRpc; - ~AJsonRpc() override = default; - void SetUp() override - { - Core::File(Path).Destroy(); - // File is destroyed - - plugin = Core::Service::Create(); - jsonRpc = plugin->QueryInterface(); - ASSERT_THAT(jsonRpc, NotNull()); - JsonObject config; - config["path"] = Path; - config["maxsize"] = MaxSize; - config["maxvalue"] = MaxValue; - config["limit"] = Limit; - string configJsonStr; - config.ToString(configJsonStr); - ON_CALL(service, ConfigLine()) - .WillByDefault( - ::testing::Return(configJsonStr)); - ASSERT_THAT(plugin->Initialize(&service), Eq("")); - } - void TearDown() override - { - plugin->Deinitialize(&service); - if (jsonRpc) - jsonRpc->Release(); - plugin->Release(); - } -}; - -TEST_F(AJsonRpc, DoesNotGetValueInUnknownNamespace) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "getValue", "{\"namespace\":\"x\",\"key\":\"x\"}", response), Eq(Core::ERROR_NOT_EXIST)); -} - -TEST_F(AJsonRpc, DoesNotGetValueWhenMandatoryParamsAreMissing) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "getValue", "{\"key\":\"x\"}", response), Eq(Core::ERROR_INVALID_INPUT_LENGTH)); - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "getValue", "{\"namespace\":\"x\"}", response), Eq(Core::ERROR_INVALID_INPUT_LENGTH)); -} - -TEST_F(AJsonRpc, DoesNotSetEmptyNamespace) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "setValue", "{\"namespace\":\"\",\"key\":\"x\",\"value\":\"x\"}", response), Eq(Core::ERROR_INVALID_INPUT_LENGTH)); -} - -TEST_F(AJsonRpc, DoesNotSetEmptyKey) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "setValue", "{\"namespace\":\"x\",\"key\":\"\",\"value\":\"x\"}", response), Eq(Core::ERROR_INVALID_INPUT_LENGTH)); -} - -TEST_F(AJsonRpc, DoesNotSetWhenMandatoryParamsAreMissing) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "setValue", "{\"key\":\"x\",\"value\":\"x\"}", response), Eq(Core::ERROR_INVALID_INPUT_LENGTH)); - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "setValue", "{\"namespace\":\"x\",\"value\":\"x\"}", response), Eq(Core::ERROR_INVALID_INPUT_LENGTH)); - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "setValue", "{\"namespace\":\"x\",\"key\":\"x\"}", response), Eq(Core::ERROR_INVALID_INPUT_LENGTH)); -} - -TEST_F(AJsonRpc, DoesNotGetKeysWhenMandatoryParamsAreMissing) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "getKeys", "", response), Eq(Core::ERROR_INVALID_INPUT_LENGTH)); -} - -TEST_F(AJsonRpc, GetsKeysForUnknownNamespaceWithoutError) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "getKeys", "{\"namespace\":\"x\"}", response), Eq(Core::ERROR_NONE)); - EXPECT_THAT(response, Eq("{\"success\":true}")); -} - -TEST_F(AJsonRpc, GetsNamespacesWhenEmptyWithoutError) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "getNamespaces", "", response), Eq(Core::ERROR_NONE)); - EXPECT_THAT(response, Eq("{\"success\":true}")); -} - -TEST_F(AJsonRpc, GetsStorageSizesWhenEmptyWithoutError) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "getStorageSizes", "", response), Eq(Core::ERROR_NONE)); - EXPECT_THAT(response, Eq("{}")); -} - -// Deprecated -TEST_F(AJsonRpc, GetsStorageSizeWhenEmptyWithoutError) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "getStorageSize", "", response), Eq(Core::ERROR_NONE)); - EXPECT_THAT(response, Eq("{\"namespaceSizes\":{},\"success\":true}")); -} - -TEST_F(AJsonRpc, DoesNotGetLimitForUnknownNamespace) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "getNamespaceStorageLimit", "{\"namespace\":\"x\"}", response), Eq(Core::ERROR_NOT_EXIST)); -} - -TEST_F(AJsonRpc, DoesNotGetLimitWhenMandatoryParamsAreMissing) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "getNamespaceStorageLimit", "", response), Eq(Core::ERROR_INVALID_INPUT_LENGTH)); -} - -TEST_F(AJsonRpc, DoesNotSetLimitWhenMandatoryParamsAreMissing) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "setNamespaceStorageLimit", "", response), Eq(Core::ERROR_INVALID_INPUT_LENGTH)); -} - -TEST_F(AJsonRpc, DoesNotDeleteKeyWhenMandatoryParamsAreMissing) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "deleteKey", "{\"key\":\"x\"}", response), Eq(Core::ERROR_INVALID_INPUT_LENGTH)); - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "deleteKey", "{\"namespace\":\"x\"}", response), Eq(Core::ERROR_INVALID_INPUT_LENGTH)); -} - -TEST_F(AJsonRpc, DoesNotDeleteNamespaceWhenMandatoryParamsAreMissing) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "deleteNamespace", "", response), Eq(Core::ERROR_INVALID_INPUT_LENGTH)); -} - -TEST_F(AJsonRpc, FlushesCacheWithoutError) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "flushCache", "", response), Eq(Core::ERROR_NONE)); - EXPECT_THAT(response, Eq("{\"success\":true}")); -} - -TEST_F(AJsonRpc, GetsDefaultLimitForExistingNamespace) -{ - string response; - ASSERT_THAT(jsonRpc->Invoke(0, 0, "", "setValue", "{\"namespace\":\"ns1\",\"key\":\"key1\",\"value\":\"value1\"}", response), Eq(Core::ERROR_NONE)); - ASSERT_THAT(response, Eq("{\"success\":true}")); - // Namespace added - - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "getNamespaceStorageLimit", "{\"namespace\":\"ns1\"}", response), Eq(Core::ERROR_NONE)); - EXPECT_THAT(response, Eq("{\"storageLimit\":50}")); -} - -TEST_F(AJsonRpc, GetsLimitThatWasSet) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "setNamespaceStorageLimit", "{\"namespace\":\"ns1\",\"storageLimit\":\"10\"}", response), Eq(Core::ERROR_NONE)); - // Namespace limit set - - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "getNamespaceStorageLimit", "{\"namespace\":\"ns1\"}", response), Eq(Core::ERROR_NONE)); - EXPECT_THAT(response, Eq("{\"storageLimit\":10}")); -} - -class AJsonRpcWithValues : public AJsonRpc { -protected: - ~AJsonRpcWithValues() override = default; - void SetUp() override - { - AJsonRpc::SetUp(); - string response; - ASSERT_THAT(jsonRpc->Invoke(0, 0, "", "setValue", "{\"namespace\":\"ns1\",\"key\":\"key1\",\"value\":\"value1\"}", response), Eq(Core::ERROR_NONE)); - ASSERT_THAT(response, Eq("{\"success\":true}")); - ASSERT_THAT(jsonRpc->Invoke(0, 0, "", "setValue", "{\"namespace\":\"ns1\",\"key\":\"key2\",\"value\":\"value2\"}", response), Eq(Core::ERROR_NONE)); - ASSERT_THAT(response, Eq("{\"success\":true}")); - ASSERT_THAT(jsonRpc->Invoke(0, 0, "", "setValue", "{\"namespace\":\"ns2\",\"key\":\"key1\",\"value\":\"value1\"}", response), Eq(Core::ERROR_NONE)); - ASSERT_THAT(response, Eq("{\"success\":true}")); - } -}; - -TEST_F(AJsonRpcWithValues, GetsValues) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "getValue", "{\"namespace\":\"ns1\",\"key\":\"key1\"}", response), Eq(Core::ERROR_NONE)); - EXPECT_THAT(response, Eq("{\"value\":\"value1\",\"success\":true}")); - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "getValue", "{\"namespace\":\"ns1\",\"key\":\"key2\"}", response), Eq(Core::ERROR_NONE)); - EXPECT_THAT(response, Eq("{\"value\":\"value2\",\"success\":true}")); - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "getValue", "{\"namespace\":\"ns2\",\"key\":\"key1\"}", response), Eq(Core::ERROR_NONE)); - EXPECT_THAT(response, Eq("{\"value\":\"value1\",\"success\":true}")); -} - -TEST_F(AJsonRpcWithValues, DoesNotGetUnknownValueInExistingNamespace) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "getValue", "{\"namespace\":\"ns1\",\"key\":\"x\"}", response), Eq(Core::ERROR_UNKNOWN_KEY)); -} - -TEST_F(AJsonRpcWithValues, GetsKeys) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "getKeys", "{\"namespace\":\"ns1\"}", response), Eq(Core::ERROR_NONE)); - EXPECT_THAT(response, Eq("{\"keys\":[\"key1\",\"key2\"],\"success\":true}")); -} - -TEST_F(AJsonRpcWithValues, GetsNamespaces) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "getNamespaces", "", response), Eq(Core::ERROR_NONE)); - EXPECT_THAT(response, Eq("{\"namespaces\":[\"ns1\",\"ns2\"],\"success\":true}")); -} - -TEST_F(AJsonRpcWithValues, GetsStorageSizes) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "getStorageSizes", "", response), Eq(Core::ERROR_NONE)); - EXPECT_THAT(response, Eq("{\"storageList\":[{\"namespace\":\"ns1\",\"size\":20},{\"namespace\":\"ns2\",\"size\":10}]}")); -} - -// Deprecated -TEST_F(AJsonRpcWithValues, GetsStorageSize) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "getStorageSize", "", response), Eq(Core::ERROR_NONE)); - EXPECT_THAT(response, Eq("{\"namespaceSizes\":{\"ns1\":20,\"ns2\":10},\"success\":true}")); -} - -TEST_F(AJsonRpcWithValues, DoesNotGetDeletedValue) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "deleteKey", "{\"namespace\":\"ns1\",\"key\":\"key1\"}", response), Eq(Core::ERROR_NONE)); - EXPECT_THAT(response, Eq("{\"success\":true}")); - // Value is deleted - - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "getValue", "{\"namespace\":\"ns1\",\"key\":\"key1\"}", response), Eq(Core::ERROR_UNKNOWN_KEY)); -} - -TEST_F(AJsonRpcWithValues, DoesNotGetValueInDeletedNamespace) -{ - string response; - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "deleteNamespace", "{\"namespace\":\"ns1\"}", response), Eq(Core::ERROR_NONE)); - EXPECT_THAT(response, Eq("{\"success\":true}")); - // Namespace is deleted - - EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "getValue", "{\"namespace\":\"ns1\",\"key\":\"key1\"}", response), Eq(Core::ERROR_NOT_EXIST)); -} diff --git a/PersistentStore/l1test/StoreTest.cpp b/PersistentStore/l1test/StoreTest.cpp deleted file mode 100644 index 3219d736f5..0000000000 --- a/PersistentStore/l1test/StoreTest.cpp +++ /dev/null @@ -1,149 +0,0 @@ -#include -#include - -#include "../PersistentStore.h" - -#include "ServiceMock.h" - -using namespace WPEFramework; -using namespace WPEFramework::Plugin; - -using ::testing::Eq; -using ::testing::NiceMock; -using ::testing::NotNull; -using ::testing::Test; - -const std::string Path = "/tmp/persistentstore/l1test/storetest"; -const uint32_t MaxSize = 100; -const uint32_t MaxValue = 10; -const uint32_t Limit = 50; - -class AStore : public Test { -protected: - NiceMock service; - PluginHost::IPlugin* plugin; - Exchange::IStore* store; - ~AStore() override = default; - void SetUp() override - { - Core::File(Path).Destroy(); - // File is destroyed - - plugin = Core::Service::Create(); - JsonObject config; - config["path"] = Path; - config["maxsize"] = MaxSize; - config["maxvalue"] = MaxValue; - config["limit"] = Limit; - string configJsonStr; - config.ToString(configJsonStr); - ON_CALL(service, ConfigLine()) - .WillByDefault( - ::testing::Return(configJsonStr)); - ASSERT_THAT(plugin->Initialize(&service), Eq("")); - store = plugin->QueryInterface(); - ASSERT_THAT(store, NotNull()); - } - void TearDown() override - { - if (store) - store->Release(); - plugin->Deinitialize(&service); - plugin->Release(); - } -}; - -class AStoreWithNotification : public AStore { -protected: - class StoreNotification : public Exchange::IStore::INotification { - public: - Core::Event _valueChanged; - string _ns; - string _key; - string _value; - StoreNotification() - : _valueChanged(false, true) - { - } - void ValueChanged(const string& ns, const string& key, const string& value) override - { - _valueChanged.SetEvent(); - _ns = ns; - _key = key; - _value = value; - } - void StorageExceeded() override - { - } - - BEGIN_INTERFACE_MAP(StoreNotification) - INTERFACE_ENTRY(Exchange::IStore::INotification) - END_INTERFACE_MAP - }; - Core::Sink sink; - ~AStoreWithNotification() override = default; - void SetUp() override - { - AStore::SetUp(); - store->Register(&sink); - } - void TearDown() override - { - store->Unregister(&sink); - AStore::TearDown(); - } -}; - -TEST_F(AStoreWithNotification, TriggersNotificationWhenValueIsSet) -{ - ASSERT_THAT(store->SetValue("ns1", "key1", "value1"), Eq(Core::ERROR_NONE)); - // Value is set - - EXPECT_THAT(sink._valueChanged.Lock(100), Eq(Core::ERROR_NONE)); - EXPECT_THAT(sink._ns, Eq("ns1")); - EXPECT_THAT(sink._key, Eq("key1")); - EXPECT_THAT(sink._value, Eq("value1")); -} - -class AStoreWithValue : public AStore { -protected: - ~AStoreWithValue() override = default; - void SetUp() override - { - AStore::SetUp(); - ASSERT_THAT(store->SetValue("ns1", "key1", "value1"), Eq(Core::ERROR_NONE)); - } -}; - -TEST_F(AStoreWithValue, GetsValue) -{ - string value; - EXPECT_THAT(store->GetValue("ns1", "key1", value), Eq(Core::ERROR_NONE)); - EXPECT_THAT(value, Eq("value1")); -} - -TEST_F(AStoreWithValue, UpdatesValue) -{ - EXPECT_THAT(store->SetValue("ns1", "key1", "a"), Eq(Core::ERROR_NONE)); - string value; - EXPECT_THAT(store->GetValue("ns1", "key1", value), Eq(Core::ERROR_NONE)); - EXPECT_THAT(value, Eq("a")); -} - -TEST_F(AStoreWithValue, DoesNotGetDeletedValue) -{ - EXPECT_THAT(store->DeleteKey("ns1", "key1"), Eq(Core::ERROR_NONE)); - // Value is deleted - - string value; - EXPECT_THAT(store->GetValue("ns1", "key1", value), Eq(Core::ERROR_UNKNOWN_KEY)); -} - -TEST_F(AStoreWithValue, DoesNotGetValueInDeletedNamespace) -{ - EXPECT_THAT(store->DeleteNamespace("ns1"), Eq(Core::ERROR_NONE)); - // Namespace is deleted - - string value; - EXPECT_THAT(store->GetValue("ns1", "key1", value), Eq(Core::ERROR_NOT_EXIST)); -} diff --git a/PersistentStore/sqlite/ServiceRegistration.cpp b/PersistentStore/sqlite/ServiceRegistration.cpp new file mode 100644 index 0000000000..edeebfa2ad --- /dev/null +++ b/PersistentStore/sqlite/ServiceRegistration.cpp @@ -0,0 +1,48 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2022 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../Module.h" +#include "Handle.h" +#include "Store2Type.h" +#include "Store2WithReconnectType.h" +#if defined(WITH_CLOCK_SYNC) +#include "Store2WithClockSyncType.h" +#endif +#include "StoreCacheType.h" +#include "StoreInspectorType.h" +#include "StoreLimitType.h" + +#if defined(WITH_CLOCK_SYNC) +class DeviceStore2 : public WPEFramework::Plugin::Sqlite::Store2WithClockSyncType>> { +}; +#else +class DeviceStore2 : public WPEFramework::Plugin::Sqlite::Store2WithReconnectType> { +}; +#endif +class DeviceStoreCache : public WPEFramework::Plugin::Sqlite::StoreCacheType { +}; +class DeviceStoreInspector : public WPEFramework::Plugin::Sqlite::StoreInspectorType { +}; +class DeviceStoreLimit : public WPEFramework::Plugin::Sqlite::StoreLimitType { +}; + +SERVICE_REGISTRATION(DeviceStore2, 1, 0); +SERVICE_REGISTRATION(DeviceStoreCache, 1, 0); +SERVICE_REGISTRATION(DeviceStoreInspector, 1, 0); +SERVICE_REGISTRATION(DeviceStoreLimit, 1, 0);