Skip to content

Commit

Permalink
fix: Native SDK unit tests support framework (#223)
Browse files Browse the repository at this point in the history
* Native sdk unit tests framework initial commit

* Fixing the buffer overflow in rpc dereferencing

* Updated schema validator to loop through params in request

* Removing commented code, adding ifdef conditions

* feat: Update mock transport and cmakes

* feat: Single file for transport logic

* feat: Update transport for test app to run

* Review fix: Removing log prints, adding comments

* Native SDK events unit tests initial commit

* Comment changes

* feat: Add standard str validator for format types

---------

Co-authored-by: Keaton Sentak <[email protected]>
  • Loading branch information
parag-pv and ksentak authored Oct 16, 2024
1 parent b43349b commit 819cc21
Show file tree
Hide file tree
Showing 5 changed files with 675 additions and 270 deletions.
76 changes: 70 additions & 6 deletions languages/cpp/src/shared/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,75 @@ cmake_minimum_required(VERSION 3.3)

project(Firebolt)

# set(CMAKE_VERBOSE_MAKEFILE ON)
set(FIREBOLT_TRANSPORT_WAITTIME 1000 CACHE STRING "Maximum time to wait for Transport layer to get response")
set(FIREBOLT_LOGLEVEL "Info" CACHE STRING "Log level to be enabled")

# Default options
option(FIREBOLT_ENABLE_STATIC_LIB "Create Firebolt library as Static library" OFF)
option(ENABLE_TESTS "Build openrpc native test" OFF)
option(ENABLE_TESTS "Build openrpc native test" ON)
option(ENABLE_UNIT_TESTS "Enable unit test" ON)
option(ENABLE_COVERAGE "Enable code coverage build." OFF)

if (FIREBOLT_ENABLE_STATIC_LIB)
set(FIREBOLT_LIBRARY_TYPE STATIC)
else ()
set(FIREBOLT_LIBRARY_TYPE SHARED)
endif ()

if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${SYSROOT_PATH}/usr" CACHE INTERNAL "" FORCE)
set(CMAKE_PREFIX_PATH ${SYSROOT_PATH}/usr/lib/cmake CACHE INTERNAL "" FORCE)
include(FetchContent)

message("Fetching nlohmann json... ")
set(nlohmann_json_VERSION v3.11.3 CACHE STRING "Fetch nlohmann::json version")
FetchContent_Declare(
nlohmann_json
GIT_REPOSITORY https://github.com/nlohmann/json
GIT_TAG ${nlohmann_json_VERSION}
)
FetchContent_GetProperties(nlohmann_json)
if(NOT nlohmann_json)
FetchContent_Populate(nlohmann_json)
add_subdirectory(${nlohmann_json_SOURCE_DIR} ${nlohmann_json_BUILD_DIR})
endif()
FetchContent_MakeAvailable(nlohmann_json)

message("Fetching nlohmann json-schema-validator... ")
FetchContent_Declare(
nlohmann_json_schema_validator
GIT_REPOSITORY https://github.com/pboettch/json-schema-validator.git
GIT_TAG 2.3.0
)
FetchContent_GetProperties(nlohmann_json_schema_validator)
if(NOT nlohmann_json_schema_validator)
FetchContent_Populate(nlohmann_json_schema_validator)
add_subdirectory(${nlohmann_json_schema_validator_SOURCE_DIR} ${nlohmann_json_schema_validator_BUILD_DIR})
endif()
FetchContent_MakeAvailable(nlohmann_json_schema_validator)

message("Fetching googletest... ")
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest
GIT_TAG v1.15.2
)
FetchContent_GetProperties(googletest)
if(NOT googletest_POPULATED)
FetchContent_Populate(googletest)
add_subdirectory(${googletest_SOURCE_DIR} ${google_BUILD_DIR})
endif()
FetchContent_MakeAvailable(googletest)

include_directories(
${nlohmann_json_SOURCE_DIR}/include
${nlohmann_json_schema_validator_SOURCE_DIR}/src
${googletest_SOURCE_DIR}/googletest/include
${googletest_SOURCE_DIR}/googlemock/include
)

# if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX ${SYSROOT_PATH}/usr CACHE INTERNAL "" FORCE)
set(CMAKE_PREFIX_PATH ${SYSROOT_PATH}/usr/lib/cmake CACHE INTERNAL "" FORCE)
# endif()

list(APPEND CMAKE_MODULE_PATH
"${CMAKE_SOURCE_DIR}/cmake"
Expand All @@ -42,15 +96,25 @@ include(HelperFunctions)

set(FIREBOLT_NAMESPACE ${PROJECT_NAME} CACHE STRING "Namespace of the project")

message("CMAKE_PREFIX_PATH: " ${CMAKE_PREFIX_PATH})

find_package(WPEFramework CONFIG REQUIRED)

if (ENABLE_TESTS AND ENABLE_COVERAGE)
include(CodeCoverage)
append_coverage_compiler_flags()
endif()

add_subdirectory(src)

if (ENABLE_TESTS)
add_subdirectory(test)
enable_testing()
add_subdirectory(test)
endif()



# make sure others can make use cmake settings of Firebolt OpenRPC
configure_file( "${CMAKE_SOURCE_DIR}/cmake/project.cmake.in"
"${CMAKE_BINARY_DIR}/${FIREBOLT_NAMESPACE}Config.cmake"
@ONLY)
@ONLY)
191 changes: 191 additions & 0 deletions languages/cpp/src/shared/include/json_engine.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
#include<iostream>
#include <fstream>

#include "gtest/gtest.h"
#include "gmock/gmock.h"

#include <nlohmann/json.hpp>
#include <nlohmann/json-schema.hpp>

using nlohmann::json;
using nlohmann::json_schema::json_validator;
using namespace ::testing;

#define REMOVE_QUOTES(s) (s.substr(1, s.length() - 2))
#define STRING_TO_BOOL(s) (s == "true" ? true : false)


inline std::string capitalizeFirstChar(std::string str) {
if (!str.empty()) {
str[0] = std::toupper(str[0]);
}
return str;
}


class JsonEngine
{
private:
std::ifstream _file;
nlohmann::json _data;

public:

JsonEngine()
{
_data = read_json_from_file("../../firebolt-core-open-rpc.json");
}

~JsonEngine(){
if (_file.is_open())
_file.close();
}

std::string get_value(const std::string& method_name)
{
for (const auto &method : _data["methods"])
{
if (method.contains("name") && (method["name"] == method_name))
{
auto value = method["examples"][0]["result"]["value"];
return value.dump();
}
}
return "";
}

json read_json_from_file(const std::string &filename)
{
std::ifstream file(filename);
if (!file.is_open())
{
throw std::runtime_error("Could not open file: " + filename);
}

json j;
file >> j;
return j;
}

json resolve_reference(const json &full_schema, const std::string &ref)
{
if (ref.find("#/") != 0)
{
throw std::invalid_argument("Only internal references supported");
}

std::string path = ref.substr(2);
std::istringstream ss(path);
std::string token;
json current = full_schema;

while (std::getline(ss, token, '/'))
{
if (current.contains(token))
{
current = current[token];
}
else
{
throw std::invalid_argument("Invalid reference path: " + ref);
}
}

return current;
}

json process_schema(json schema, const json &full_schema)
{
if (schema.is_object())
{
if (schema.contains("$ref"))
{
std::string ref = schema["$ref"];
schema = resolve_reference(full_schema, ref);
}

for (auto &el : schema.items())
{
el.value() = process_schema(el.value(), full_schema);
}
}
else if (schema.is_array())
{
for (auto &el : schema)
{
el = process_schema(el, full_schema);
}
}

return schema;
}


#ifdef UNIT_TEST

// template <typename RESPONSE>
void MockRequest(const WPEFramework::Core::JSONRPC::Message* message)
{
std::string methodName = capitalizeFirstChar(message->Designator.Value().c_str());

/* TODO: Add a flag here that will be set to true if the method name is found in the rpc block, u
Use the flag to validate "Method not found" or other errors from SDK if applicable */
for (const auto &method : _data["methods"])
{
if (method.contains("name") && (method["name"] == methodName))
{
// Method name validation
EXPECT_EQ(methodName, method["name"]);

// ID Validation
// TODO: Check if id gets incremented by 1 for each request
EXPECT_THAT(message->Id, AllOf(Ge(1),Le(std::numeric_limits<int>::max())));

// Schema validation
const json requestParams = json::parse(message->Parameters.Value());
if(method["params"].empty()) {
std::cout << "Schema validation for empty parameters" << std::endl;
EXPECT_EQ(requestParams, "{}"_json);
}
else {
json_validator validator(nullptr, nlohmann::json_schema::default_string_format_check);
const json openRPCParams = method["params"];
for (auto& item : openRPCParams.items()) {
std::string key = item.key();
json currentSchema = item.value();
std::string paramName = currentSchema["name"];
if (requestParams.contains(paramName)) {
json dereferenced_schema = process_schema(currentSchema, _data);
try{
validator.set_root_schema(dereferenced_schema["schema"]);
validator.validate(requestParams[paramName]);
std::cout << "Schema validation succeeded" << std::endl;
}
catch (const std::exception &e){
FAIL() << "Schema validation error: " << e.what() << std::endl;
}
}
}
}
}
}
}

template <typename RESPONSE>
Firebolt::Error MockResponse(WPEFramework::Core::JSONRPC::Message &message, RESPONSE &response)
{
std::string methodName = capitalizeFirstChar(message.Designator.Value().c_str());

// Loop through the methods to find the one with the given name
for (const auto &method : _data["methods"])
{
if (method.contains("name") && (method["name"] == methodName))
{
message.Result = method["examples"][0]["result"]["value"].dump();
}
}
return Firebolt::Error::None;
}
#endif
};

8 changes: 7 additions & 1 deletion languages/cpp/src/shared/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,23 @@ add_library(${TARGET} ${FIREBOLT_LIBRARY_TYPE}
Async/Async.cpp
)

if(ENABLE_UNIT_TESTS)
target_compile_definitions(FireboltSDK PRIVATE UNIT_TEST)
endif()

set(CMAKE_POSITION_INDEPENDENT_CODE ON)

find_package(${NAMESPACE}WebSocket CONFIG REQUIRED)
find_package(${NAMESPACE}WebSocket CONFIG REQUIRED)
find_package(${NAMESPACE}Core CONFIG REQUIRED)
find_package(${NAMESPACE}Cryptalgo CONFIG REQUIRED)

target_link_libraries(${TARGET}
PUBLIC
${NAMESPACE}WebSocket::${NAMESPACE}WebSocket
${NAMESPACE}Core::${NAMESPACE}Core
${NAMESPACE}WebSocket::${NAMESPACE}WebSocket
${NAMESPACE}Cryptalgo::${NAMESPACE}Cryptalgo
)

target_include_directories(${TARGET}
Expand Down Expand Up @@ -76,4 +82,4 @@ install(

InstallHeaders(TARGET ${TARGET} HEADERS . NAMESPACE ${FIREBOLT_NAMESPACE} DESTINATION ${FIREBOLT_NAMESPACE}SDK)
InstallCMakeConfig(TARGETS ${TARGET})
InstallPackageConfig(TARGETS ${TARGET} DESCRIPTION "Firebolt SDK Library")
InstallPackageConfig(TARGETS ${TARGET} DESCRIPTION "Firebolt SDK Library")
Loading

0 comments on commit 819cc21

Please sign in to comment.