From 485ffa5781960a0ef588403e129adfc5174a82a8 Mon Sep 17 00:00:00 2001 From: Cameron Devine Date: Fri, 10 Apr 2026 12:20:48 -0400 Subject: [PATCH 1/2] Quick example of moving the json serialization and deserialization from RtxJsonUtilities to the classes. --- CMakeLists.txt | 1 + src/Clock.cpp | 25 +++++++-- src/Clock.h | 11 ++-- test/test_clock.cpp | 126 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 156 insertions(+), 7 deletions(-) create mode 100644 test/test_clock.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 50e8fa22..c37e64c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -122,6 +122,7 @@ add_executable(tsflib_test ./test/test_main.cpp ./test/test_units.cpp ./test/test_curve.cpp + ./test/test_clock.cpp ./test/test_record.cpp ./test/test_influx.cpp ./test/test_filters.cpp diff --git a/src/Clock.cpp b/src/Clock.cpp index fe57b6f7..01d81603 100755 --- a/src/Clock.cpp +++ b/src/Clock.cpp @@ -39,7 +39,7 @@ std::ostream& TSF::operator<< (std::ostream &out, Clock &clock) { #pragma mark - Public Methods -std::string Clock::name() { +std::string Clock::name() const { return _name; } @@ -86,7 +86,7 @@ bool Clock::isValid(time_t time) { } -int Clock::period() { +int Clock::period() const { return _period; } @@ -97,7 +97,7 @@ void Clock::setPeriod(int p) { } } -time_t Clock::start() { +time_t Clock::start() const { return _start; } @@ -165,4 +165,21 @@ std::ostream& Clock::toStream(std::ostream &stream) { time_t Clock::timeOffset(time_t time) { return ( (time - (start() % period())) % period() ); -} \ No newline at end of file +} + + +#pragma mark - JSON Serialization + +void TSF::to_json(nlohmann::json& j, const Clock& c) { + j = nlohmann::json{ + {"name", c.name()}, + {"period", c.period()}, + {"offset", static_cast(c.start())} + }; +} + +void TSF::from_json(const nlohmann::json& j, Clock& c) { + c.setName(j.at("name").get()); + c.setPeriod(j.at("period").get()); + c.setStart(j.at("offset").get()); +} diff --git a/src/Clock.h b/src/Clock.h index 40f608d4..74134bac 100755 --- a/src/Clock.h +++ b/src/Clock.h @@ -14,6 +14,7 @@ #include #include "tsfMacros.h" #include "TimeRange.h" +#include namespace TSF { @@ -89,7 +90,7 @@ namespace TSF { bool isEqual(Clock::_sp other); - std::string name(); + std::string name() const; void setName(std::string name); virtual bool isCompatibleWith(Clock::_sp clock); @@ -99,9 +100,9 @@ namespace TSF { virtual time_t timeBefore(time_t time); - int period(); + int period() const; void setPeriod(int p); - time_t start(); + time_t start() const; void setStart(time_t startTime); virtual std::set< time_t > timeValuesInRange(TimeRange range); virtual std::ostream& toStream(std::ostream &stream); @@ -116,6 +117,10 @@ namespace TSF { }; std::ostream& operator<< (std::ostream &out, Clock &clock); + + // JSON serialization - Clock owns its own data shape + void to_json(nlohmann::json& j, const Clock& c); + void from_json(const nlohmann::json& j, Clock& c); } #endif diff --git a/test/test_clock.cpp b/test/test_clock.cpp new file mode 100644 index 00000000..4156b0e3 --- /dev/null +++ b/test/test_clock.cpp @@ -0,0 +1,126 @@ +#include "test_main.h" +#include "Clock.h" + +using namespace TSF; +using namespace std; +using namespace nlohmann; + +//////////////////////// +// clock serialization +BOOST_AUTO_TEST_SUITE(clock_serialization) + +BOOST_AUTO_TEST_CASE(to_json_basic) { + Clock c(3600, 60); + c.setName("hourly"); + + json j = c; + + BOOST_CHECK_EQUAL(j["name"].get(), "hourly"); + BOOST_CHECK_EQUAL(j["period"].get(), 3600); + BOOST_CHECK_EQUAL(j["offset"].get(), 60); +} + +BOOST_AUTO_TEST_CASE(from_json_basic) { + json j = { + {"name", "five_min"}, + {"period", 300}, + {"offset", 0} + }; + + Clock c = j.get(); + + BOOST_CHECK_EQUAL(c.name(), "five_min"); + BOOST_CHECK_EQUAL(c.period(), 300); + BOOST_CHECK_EQUAL(c.start(), 0); +} + +BOOST_AUTO_TEST_CASE(roundtrip) { + Clock original(900, 30); + original.setName("quarter_hour"); + + // serialize then deserialize + json j = original; + Clock restored = j.get(); + + BOOST_CHECK_EQUAL(restored.name(), original.name()); + BOOST_CHECK_EQUAL(restored.period(), original.period()); + BOOST_CHECK_EQUAL(restored.start(), original.start()); +} + +BOOST_AUTO_TEST_CASE(roundtrip_preserves_isRegular) { + // setPeriod sets _isRegular = true when period > 0 + // make sure roundtrip through json preserves this behavior + Clock c(7200, 0); + c.setName("two_hour"); + + json j = c; + Clock restored = j.get(); + + // isValid depends on _isRegular being set correctly via setPeriod + BOOST_CHECK_EQUAL(restored.isValid(0), true); + BOOST_CHECK_EQUAL(restored.isValid(7200), true); + BOOST_CHECK_EQUAL(restored.isValid(100), false); +} + +BOOST_AUTO_TEST_CASE(to_json_default_clock) { + Clock c; // defaults: period=3600, start=0 + + json j = c; + + BOOST_CHECK_EQUAL(j["name"].get(), ""); + BOOST_CHECK_EQUAL(j["period"].get(), 3600); + BOOST_CHECK_EQUAL(j["offset"].get(), 0); +} + +BOOST_AUTO_TEST_CASE(from_json_missing_key_throws) { + json j = { + {"name", "bad"}, + {"period", 300} + // missing "offset" + }; + + BOOST_CHECK_THROW(j.get(), json::out_of_range); +} + +BOOST_AUTO_TEST_CASE(to_json_zero_period) { + Clock c(0, 0); + c.setName("irregular"); + + json j = c; + + BOOST_CHECK_EQUAL(j["period"].get(), 0); +} + +BOOST_AUTO_TEST_CASE(to_json_only_contains_expected_keys) { + Clock c(3600, 0); + c.setName("test"); + + json j = c; + + BOOST_CHECK_EQUAL(j.size(), 3); + BOOST_CHECK(j.contains("name")); + BOOST_CHECK(j.contains("period")); + BOOST_CHECK(j.contains("offset")); +} + +BOOST_AUTO_TEST_CASE(from_json_ignores_extra_keys) { + json j = { + {"name", "test"}, + {"period", 600}, + {"offset", 10}, + {"class", "clock"}, + {"self", {{"uid", 1}, {"ref", "clock"}}} + }; + + // should not throw — extra keys are ignored + Clock c = j.get(); + + BOOST_CHECK_EQUAL(c.name(), "test"); + BOOST_CHECK_EQUAL(c.period(), 600); + BOOST_CHECK_EQUAL(c.start(), 10); +} + +BOOST_AUTO_TEST_SUITE_END() +// clock_serialization +///////////////////////// + From f184a4d4e23575cce6b313cd291ca10d3f2ce553 Mon Sep 17 00:00:00 2001 From: Cameron Devine Date: Mon, 13 Apr 2026 14:15:29 -0400 Subject: [PATCH 2/2] Refactor JSON serialization for Clock class to use glaze. Update to c++23 for glaze. Update InfluxTcpAdapter to use httplib instead of oatpp with the update to 23. Remove oatpp and curl dependencies. --- CMakeLists.txt | 16 +- conanfile.py | 9 +- src/Clock.cpp | 17 -- src/Clock.h | 15 +- src/Components.hpp | 76 --------- src/InfluxAdapter.cpp | 376 ++++++++++++++++-------------------------- src/InfluxAdapter.h | 32 +--- src/InfluxClient.hpp | 44 ----- test/CMakeLists.txt | 5 +- test/MyClientTest.cpp | 69 -------- test/MyClientTest.hpp | 22 --- test/TestClient.h | 34 ---- test/TestController.h | 93 ----------- test/test_clock.cpp | 101 ++++++------ test/test_influx.cpp | 120 -------------- 15 files changed, 223 insertions(+), 806 deletions(-) delete mode 100644 src/Components.hpp delete mode 100644 src/InfluxClient.hpp delete mode 100644 test/MyClientTest.cpp delete mode 100644 test/MyClientTest.hpp delete mode 100644 test/TestClient.h delete mode 100644 test/TestController.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c37e64c3..026b690f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ cmake_minimum_required (VERSION 3.15) project (TSFLIB) SET (CMAKE_C_FLAGS "-std=c99") -SET (CMAKE_CXX_FLAGS "-Wall -std=c++17") +SET (CMAKE_CXX_FLAGS "-Wall -std=c++23") SET (CMAKE_POSITION_INDEPENDENT_CODE ON) add_definitions(-DNO_MYSQL) @@ -32,13 +32,13 @@ IF(APPLE) SET(EXTRA_LIBS ${SECURITY_FRAMEWORK} ${CORE_FRAMEWORK}) ENDIF (APPLE) -find_package(CURL REQUIRED) find_package(Boost REQUIRED) find_package(SQLite3 REQUIRED) find_package(sqlite_modern_cpp REQUIRED) -find_package(oatpp REQUIRED) -find_package(oatpp-openssl REQUIRED) +find_package(httplib REQUIRED) +find_package(OpenSSL REQUIRED) find_package(nlohmann_json REQUIRED) +find_package(glaze REQUIRED) # the tsf library include_directories( @@ -97,18 +97,18 @@ add_library(tsflib set_target_properties( tsflib PROPERTIES - CXX_STANDARD 17 + CXX_STANDARD 23 ) target_compile_definitions(tsflib PRIVATE MAXFLOAT=3.40282347e+38F) set(tsflib_deps - CURL::libcurl boost::boost SQLite::SQLite3 sqlite_modern_cpp::sqlite_modern_cpp - oatpp::oatpp - oatpp::oatpp-openssl + httplib::httplib + openssl::openssl nlohmann_json::nlohmann_json + glaze::glaze ) diff --git a/conanfile.py b/conanfile.py index e5557a42..05a4b16d 100644 --- a/conanfile.py +++ b/conanfile.py @@ -1,4 +1,3 @@ -import os from conan import ConanFile from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout @@ -14,6 +13,7 @@ class TSFlib(ConanFile): default_options = { "shared": False, "fPIC": True, + "cpp-httplib/*:with_openssl": True, "boost*:without_container": True, "boost*:without_context": True, "boost*:without_contract": True, @@ -44,13 +44,12 @@ class TSFlib(ConanFile): } def requirements(self): - self.requires("zlib/1.2.13") + self.requires("zlib/1.3.1") self.requires("openssl/3.1.2") - self.requires("oatpp/1.3.0") - self.requires("oatpp-openssl/1.3.0") + self.requires("cpp-httplib/0.39.0") self.requires("boost/1.83.0") self.requires("nlohmann_json/3.10.5") - self.requires("libcurl/7.80.0") + self.requires("glaze/7.2.0") #self.requires("sqlite3/3.45.3") self.requires("sqlite_modern_cpp/3.2") diff --git a/src/Clock.cpp b/src/Clock.cpp index 01d81603..7ccb049c 100755 --- a/src/Clock.cpp +++ b/src/Clock.cpp @@ -166,20 +166,3 @@ std::ostream& Clock::toStream(std::ostream &stream) { time_t Clock::timeOffset(time_t time) { return ( (time - (start() % period())) % period() ); } - - -#pragma mark - JSON Serialization - -void TSF::to_json(nlohmann::json& j, const Clock& c) { - j = nlohmann::json{ - {"name", c.name()}, - {"period", c.period()}, - {"offset", static_cast(c.start())} - }; -} - -void TSF::from_json(const nlohmann::json& j, Clock& c) { - c.setName(j.at("name").get()); - c.setPeriod(j.at("period").get()); - c.setStart(j.at("offset").get()); -} diff --git a/src/Clock.h b/src/Clock.h index 74134bac..0650902d 100755 --- a/src/Clock.h +++ b/src/Clock.h @@ -14,7 +14,7 @@ #include #include "tsfMacros.h" #include "TimeRange.h" -#include +#include namespace TSF { @@ -118,9 +118,16 @@ namespace TSF { std::ostream& operator<< (std::ostream &out, Clock &clock); - // JSON serialization - Clock owns its own data shape - void to_json(nlohmann::json& j, const Clock& c); - void from_json(const nlohmann::json& j, Clock& c); } +template <> +struct glz::meta { + using T = TSF::Clock; + static constexpr auto value = glz::object( + "name", glz::custom<&T::setName, &T::name>, + "period", glz::custom<&T::setPeriod, &T::period>, + "offset", glz::custom<&T::setStart, &T::start> + ); +}; + #endif diff --git a/src/Components.hpp b/src/Components.hpp deleted file mode 100644 index b6a27c9d..00000000 --- a/src/Components.hpp +++ /dev/null @@ -1,76 +0,0 @@ -// -// Components.hpp -// tsflib -// -// Created by Devine, Cameron - Xylem on 2/23/22. -// - -#ifndef Components_h -#define Components_h - - -#include "oatpp/web/server/HttpConnectionHandler.hpp" - -#include "oatpp/network/virtual_/client/ConnectionProvider.hpp" -#include "oatpp/network/virtual_/server/ConnectionProvider.hpp" -#include "oatpp/network/virtual_/Interface.hpp" - -#include "oatpp/parser/json/mapping/ObjectMapper.hpp" - -#include "oatpp/core/macro/component.hpp" - -/** - * Test Components config - */ -class Components { -public: - - /** - * Create oatpp virtual network interface for test networking - */ - OATPP_CREATE_COMPONENT(std::shared_ptr, virtualInterface)([] { - return oatpp::network::virtual_::Interface::obtainShared("virtualhost"); - }()); - - /** - * Create server ConnectionProvider of oatpp virtual connections for test - */ - OATPP_CREATE_COMPONENT(std::shared_ptr, serverConnectionProvider)([] { - OATPP_COMPONENT(std::shared_ptr, interface); - return oatpp::network::virtual_::server::ConnectionProvider::createShared(interface); - }()); - - /** - * Create client ConnectionProvider of oatpp virtual connections for test - */ - OATPP_CREATE_COMPONENT(std::shared_ptr, clientConnectionProvider)([] { - OATPP_COMPONENT(std::shared_ptr, interface); - return oatpp::network::virtual_::client::ConnectionProvider::createShared(interface); - }()); - - /** - * Create Router component - */ - OATPP_CREATE_COMPONENT(std::shared_ptr, httpRouter)([] { - return oatpp::web::server::HttpRouter::createShared(); - }()); - - /** - * Create ConnectionHandler component which uses Router component to route requests - */ - OATPP_CREATE_COMPONENT(std::shared_ptr, serverConnectionHandler)([] { - OATPP_COMPONENT(std::shared_ptr, router); // get Router component - return oatpp::web::server::HttpConnectionHandler::createShared(router); - }()); - - /** - * Create ObjectMapper component to serialize/deserialize DTOs in Contoller's API - */ - OATPP_CREATE_COMPONENT(std::shared_ptr, apiObjectMapper)([] { - return oatpp::parser::json::mapping::ObjectMapper::createShared(); - }()); - -}; - - -#endif /* Components_h */ diff --git a/src/InfluxAdapter.cpp b/src/InfluxAdapter.cpp index ba15e06b..f4ef5994 100644 --- a/src/InfluxAdapter.cpp +++ b/src/InfluxAdapter.cpp @@ -11,10 +11,7 @@ #include #include -#include - #include "InfluxAdapter.h" -#include "InfluxClient.hpp" #include "MetricInfo.h" @@ -22,25 +19,11 @@ using namespace std; using namespace TSF; -using namespace oatpp; -using namespace oatpp::web; -using namespace oatpp::network; using namespace nlohmann; -std::string __url_encode(const std::string& input); -std::string __url_encode(const std::string& input) { - std::string encoded; - CURL *curl = curl_easy_init(); - if(curl) { - char *output = curl_easy_escape(curl, input.c_str(), 0); - if(output) { - encoded = std::string(output); - curl_free(output); - } - curl_easy_cleanup(curl); - } - return encoded; -} +// Resolve ambiguity with macOS MacTypes.h Point +using TSF::Point; + /***************************************************************************************/ InfluxAdapter::connectionInfo::connectionInfo() { @@ -88,8 +71,8 @@ void InfluxAdapter::setConnectionString(const std::string& str) { {"p", [&](string v){this->conn.pass = v;}}, {"validate", [&](string v){this->conn.validate = boost::lexical_cast(v);}}, {"ratelimit", [&](string v){this->conn.msec_ratelimit = boost::lexical_cast(v);}} - }); - + }); + for (auto kv : kvPairs) { if (kvSetters.count(kv.first) > 0) { kvSetters.at(kv.first)(kv.second); @@ -300,23 +283,6 @@ const char *kSHOW_SERIES = "show series"; const char *kERROR = "error"; const char *kRESULTS = "results"; // INFLUX TCP -// task wrapper impl -/**namespace TSF { - class PplxTaskWrapper : public ITaskWrapper { - public: - PplxTaskWrapper(); - pplx::task task; - }; -} -// why the fully private implementation? it's really to guard client applications from having -// to #include the pplx concurrency libs. this way everything is self-contained. -PplxTaskWrapper::PplxTaskWrapper() { - this->task = pplx::task([]() { - return; // simple no-op task as filler. - }); -} -#define INFLUX_ASYNC_SEND static_pointer_cast(_sendTask)->task - **/ std::string InfluxTcpAdapter::Query::selectStr() { @@ -348,63 +314,28 @@ std::string InfluxTcpAdapter::Query::nameAndWhereClause() { -vector __pointsSingle(json& json); - InfluxTcpAdapter::InfluxTcpAdapter( errCallback_t cb) : InfluxAdapter(cb) { - //_sendTask.reset(new PplxTaskWrapper()); } -// -InfluxTcpAdapter::InfluxTcpAdapter( errCallback_t cb, std::shared_ptr rClient ) : InfluxAdapter(cb){ - this->_restClient = rClient; -} InfluxTcpAdapter::~InfluxTcpAdapter() { } -shared_ptr InfluxTcpAdapter::createExecutor() { - if( TSF_STRINGS_ARE_EQUAL(this->conn.host, "localhost") && this->conn.port == 0){ - auto interface = oatpp::network::virtual_::Interface::obtainShared("virtualhost"); - auto clientConnectionProvider = oatpp::network::virtual_::client::ConnectionProvider::createShared(interface); - return client::HttpRequestExecutor::createShared(clientConnectionProvider); +void InfluxTcpAdapter::createHttpClient() { + string baseUrl; + if (TSF_STRINGS_ARE_EQUAL(this->conn.proto, "https")) { + baseUrl = "https://" + this->conn.host + ":" + to_string(this->conn.port); } - shared_ptr connectionProvider; - /* Create connection provider */ - if( TSF_STRINGS_ARE_EQUAL(this->conn.proto, "http") ) - { - connectionProvider = oatpp::network::tcp::client::ConnectionProvider::createShared({this->conn.host, - (v_uint16)this->conn.port}); + else { + baseUrl = "http://" + this->conn.host + ":" + to_string(this->conn.port); } - else if( TSF_STRINGS_ARE_EQUAL(this->conn.proto, "https") ) - { - auto config = oatpp::openssl::Config::createShared(); - connectionProvider = oatpp::openssl::client::ConnectionProvider::createShared(config, {this->conn.host, (v_uint16)this->conn.port}); - } - - auto monitor = std::make_shared(connectionProvider); - - /* close all connections that stay opened for more than 120 seconds */ - monitor->addMetricsChecker( - std::make_shared( - std::chrono::seconds(20) - ) - ); -// auto monitor = std::make_shared(connectionProvider); -// monitor->addMetricsChecker( -// std::make_shared( -// std::chrono::seconds(20), -// std::chrono::seconds(20) -// ) -// ); - /* create retry policy */ - auto retryPolicy = std::make_shared(5 /* max retries */, std::chrono::seconds(5) /* retry interval */); - - auto connectionPool = oatpp::network::ClientConnectionPool::createShared(connectionProvider, 10, std::chrono::seconds(20)); - /* create request executor */ - cout << "Creating executor" << endl; - return client::HttpRequestExecutor::createShared(connectionPool, retryPolicy); + _httpClient = std::make_unique(baseUrl); + _httpClient->set_connection_timeout(TSF_INFLUX_CLIENT_TIMEOUT); + _httpClient->set_read_timeout(TSF_INFLUX_CLIENT_TIMEOUT); + _httpClient->set_basic_auth(this->conn.user, this->conn.pass); + _httpClient->set_keep_alive(true); } const DbAdapter::adapterOptions InfluxTcpAdapter::options() const { @@ -436,18 +367,15 @@ void InfluxTcpAdapter::doConnect() { _connected = false; _errCallback("Connecting..."); - auto requestExecutor = createExecutor(); - auto objectMapper = oatpp::parser::json::mapping::ObjectMapper::createShared(); - _restClient = InfluxClient::createShared(requestExecutor, objectMapper); - + createHttpClient(); + // see if the database needs to be created bool dbExists = false; string q("SHOW SERIES LIMIT 1"); json jsoMeas; try { - auto response = _restClient->doQuery(this->conn.getAuthString(), this->conn.db, encodeQuery(q)); - jsoMeas = jsonFromResponse(response); + jsoMeas = executeQuery(q); } catch (const std::exception &err) { cerr << "error executing query: " << err.what() << endl; } @@ -479,9 +407,16 @@ void InfluxTcpAdapter::doConnect() { if (!dbExists) { - string q("CREATE DATABASE " + this->conn.db); - auto response = _restClient->doCreate(this->conn.getAuthString(), encodeQuery(q)); - json js = jsonFromResponse(response); + string createQ("CREATE DATABASE " + this->conn.db); + // Use POST /query for database creation (no db param needed) + httplib::Params params = {{"q", createQ}}; + auto res = _httpClient->Post("/query", httplib::Headers{}, params); + json js = json::object(); + if (res && res->status == 200) { + if (json::accept(res->body)) { + js = json::parse(res->body); + } + } if (js.size() == 0 || !js.contains(kRESULTS) ) { _errCallback("Can't create database"); return; @@ -498,31 +433,24 @@ void InfluxTcpAdapter::doConnect() { } + +nlohmann::json InfluxTcpAdapter::executeQuery(const std::string& query, const std::string& epoch) { + httplib::Params params = {{"db", this->conn.db}, {"q", query}}; + if (!epoch.empty()) { + params.emplace("epoch", epoch); + } + + auto res = _httpClient->Get("/query", params, httplib::Headers{}); + if (!res) { + cerr << TAG << ": HTTP request failed (no response)" << endl; + return json::object(); + } + return jsonFromResponseBody(res->status, res->body); +} + + IdentifierUnitsList InfluxTcpAdapter::idUnitsList() { - /* - - perform a query to get all the series. - response will be nested in terms of "measurement", and then each array in the "values" array will denote an individual time series: - - series: [ - { name: flow - columns: [asset_id, asset_type, dma, ... ] - values: [ [33410, pump, brecon, ...], - [33453, pipe, mt.\ washington, ...], - [...] - ] - }, - { name: pressure - columns: [asset_id, asset_type, dma, ...] - values: [ [44305, junction, brecon, ...], - [43205, junction, mt.\ washington, ...], - [...] - ] - } - - */ - IdentifierUnitsList ids; // if i'm busy, then don't bother. unless this could be the first query. @@ -533,8 +461,7 @@ IdentifierUnitsList InfluxTcpAdapter::idUnitsList() { json jsv; try { - auto response = _restClient->doQuery(this->conn.getAuthString(), this->conn.db, encodeQuery(kSHOW_SERIES)); - jsv = jsonFromResponse(response); + jsv = executeQuery(kSHOW_SERIES); } catch (const std::exception &err) { cerr << "error executing query: " << err.what() << endl; } @@ -581,15 +508,8 @@ IdentifierUnitsList InfluxTcpAdapter::idUnitsList() { } -std::map > InfluxTcpAdapter::wideQuery(TimeRange range) { - //_TSF_DB_SCOPED_LOCK; - - - // aggressive prefetch. query all series for some range, then shortcut subsequent queries if they are in the range cached. - - // influx allows regex in queries: - // select "value" from /.+/ where time > ... and time < ... - +std::map > InfluxTcpAdapter::wideQuery(TimeRange range) { + vector fields({"time", "value", "quality", "confidence"}); vector where({"time >= " + to_string(range.start) + "s", "time <= " + to_string(range.end) + "s"}); @@ -600,29 +520,25 @@ std::map > InfluxTcpAdapter::wideQuery(TimeRange ss << " WHERE " << boost::algorithm::join(where," AND "); ss << " GROUP BY * ORDER BY ASC"; - // for many wide query optimization needs, we may also want the last known point prior to the range provided - // such as pump status or other report-by-exception values. string prevQuery = "SELECT time, value, quality, confidence FROM /.*/ WHERE time < " + to_string(range.start) + "s GROUP BY * order by time desc limit 1"; string nextQuery = "SELECT time, value, quality, confidence FROM /.*/ WHERE time > " + to_string(range.end) + "s GROUP BY * order by time asc limit 1"; auto qstr = prevQuery + ";" + ss.str() + ";" + nextQuery; json jsv; try { - auto response = _restClient->doQueryWithTimePrecision(this->conn.getAuthString(), this->conn.db, encodeQuery(qstr), "s"); - jsv = jsonFromResponse(response); + jsv = executeQuery(qstr, "s"); } catch (const std::exception &err) { cerr << "error executing query: " << err.what() << endl; } - map > fetch = __pointsFromJson(jsv); + map > fetch = __pointsFromJson(jsv); return fetch; } // READ -std::vector InfluxTcpAdapter::selectRange(const std::string& id, TimeRange range) { - //_TSF_DB_SCOPED_LOCK; - +std::vector InfluxTcpAdapter::selectRange(const std::string& id, TimeRange range) { + string dbId = influxIdForTsId(id); InfluxTcpAdapter::Query q = this->queryPartsFromMetricId(dbId); q.where.push_back("time >= " + to_string(range.start) + "s"); @@ -630,8 +546,7 @@ std::vector InfluxTcpAdapter::selectRange(const std::string& id, TimeRang json jsv; try { - auto response = _restClient->doQueryWithTimePrecision(this->conn.getAuthString(), this->conn.db, encodeQuery(q.selectStr()), "s"); - jsv = jsonFromResponse(response); + jsv = executeQuery(q.selectStr(), "s"); } catch (const std::exception &err) { cerr << "error executing query: " << err.what() << endl; } @@ -663,10 +578,9 @@ vector _makeSelectStrs(WhereClause q) { return clauses; } -Point InfluxTcpAdapter::selectNext(const std::string& id, time_t time, WhereClause whereClause) { - //_TSF_DB_SCOPED_LOCK; - - std::vector points; +TSF::Point InfluxTcpAdapter::selectNext(const std::string& id, time_t time, WhereClause whereClause) { + + std::vector points; string dbId = influxIdForTsId(id); Query q = this->queryPartsFromMetricId(dbId); q.where.push_back("time > " + to_string(time) + "s"); @@ -681,8 +595,7 @@ Point InfluxTcpAdapter::selectNext(const std::string& id, time_t time, WhereClau json jsv; try { - auto response = _restClient->doQueryWithTimePrecision(this->conn.getAuthString(), this->conn.db, encodeQuery(q.selectStr()), "s"); - jsv = jsonFromResponse(response); + jsv = executeQuery(q.selectStr(), "s"); } catch (const std::exception &err) { cerr << "error executing query: " << err.what() << endl; } @@ -690,16 +603,15 @@ Point InfluxTcpAdapter::selectNext(const std::string& id, time_t time, WhereClau points = __pointsSingle(jsv); if (points.size() == 0) { - return Point(); + return TSF::Point(); } return points.front(); } -Point InfluxTcpAdapter::selectPrevious(const std::string& id, time_t time, WhereClause whereClause) { - //_TSF_DB_SCOPED_LOCK; - - std::vector points; +TSF::Point InfluxTcpAdapter::selectPrevious(const std::string& id, time_t time, WhereClause whereClause) { + + std::vector points; string dbId = influxIdForTsId(id); Query q = this->queryPartsFromMetricId(dbId); @@ -715,8 +627,7 @@ Point InfluxTcpAdapter::selectPrevious(const std::string& id, time_t time, Where json jsv; try { - auto response = _restClient->doQueryWithTimePrecision(this->conn.getAuthString(), this->conn.db, encodeQuery(q.selectStr()), "s"); - jsv = jsonFromResponse(response); + jsv = executeQuery(q.selectStr(), "s"); } catch (const std::exception &err) { cerr << "error executing query: " << err.what() << endl; } @@ -724,7 +635,7 @@ Point InfluxTcpAdapter::selectPrevious(const std::string& id, time_t time, Where points = __pointsSingle(jsv); if (points.size() == 0) { - return Point(); + return TSF::Point(); } return points.front(); @@ -732,14 +643,14 @@ Point InfluxTcpAdapter::selectPrevious(const std::string& id, time_t time, Where -vector InfluxTcpAdapter::selectWithQuery(const std::string& query, TimeRange range) { +vector InfluxTcpAdapter::selectWithQuery(const std::string& query, TimeRange range) { // expects a "$timeFilter" placeholder in the string arg, // to be replaced with the time range, e.g., "time >= t1 and time <= t2" //case insensitive find if (boost::ifind_first(query, std::string("$timeFilter")).empty()) { // add WHERE clause - return vector(); + return vector(); } string qStr = query; @@ -769,8 +680,7 @@ vector InfluxTcpAdapter::selectWithQuery(const std::string& query, TimeRa } json jsv; try { - auto response = _restClient->doQueryWithTimePrecision(this->conn.getAuthString(), this->conn.db, encodeQuery(qStr), "s"); - jsv = jsonFromResponse(response); + jsv = executeQuery(qStr, "s"); } catch (const std::exception &err) { cerr << "error executing query: " << err.what() << endl; } @@ -786,23 +696,28 @@ void InfluxTcpAdapter::removeRecord(const std::string& id) { stringstream sqlss; sqlss << "DROP SERIES FROM " << q.nameAndWhereClause(); - oatpp::String qStr(sqlss.str()); - _restClient->removeRecord(this->conn.getAuthString(), encodeQuery(qStr)); + string qStr = sqlss.str(); + httplib::Params params = {{"q", qStr}}; + _httpClient->Post("/query", httplib::Headers{}, params); } void InfluxTcpAdapter::removeAllRecords() { _errCallback("Truncating"); - OATPP_LOGD(TAG, "Truncating"); - + cerr << TAG << ": Truncating" << endl; + auto ids = this->idUnitsList(); stringstream sqlss; sqlss << "DROP DATABASE " << this->conn.db << "; CREATE DATABASE " << this->conn.db; string qStr(sqlss.str()); - auto response = _restClient->removeRecord(this->conn.getAuthString(), encodeQuery(qStr)); - json v = jsonFromResponse(response); - + httplib::Params params = {{"q", qStr}}; + auto res = _httpClient->Post("/query", httplib::Headers{}, params); + json v = json::object(); + if (res && res->status == 200 && json::accept(res->body)) { + v = json::parse(res->body); + } + this->beginTransaction(); for (auto ts_units : *ids.get()) { this->insertIdentifierAndUnits(ts_units.first, ts_units.second.first); @@ -818,8 +733,7 @@ size_t InfluxTcpAdapter::maxTransactionLines() { } void InfluxTcpAdapter::sendPointsWithString(const std::string& content) { - //INFLUX_ASYNC_SEND.wait(); // wait on previous send if needed. - + if(sendPointsFuture.valid()){ sendPointsFuture.wait(); sendPointsFuture.get(); @@ -837,22 +751,26 @@ void InfluxTcpAdapter::sendPointsWithString(const std::string& content) { bio::copy(out, compressed); const string zippedContent(compressed.str()); - int code; - oatpp::String desc; try { - auto response = _restClient->sendPoints(this->conn.getAuthString(), "gzip", this->conn.db, "s", zippedContent); - code = response->getStatusCode(); - desc = response->getStatusDescription(); + string path = "/write?db=" + this->conn.db + "&precision=s"; + httplib::Headers headers = { + {"Content-Encoding", "gzip"} + }; + auto res = _httpClient->Post(path, headers, zippedContent, "application/octet-stream"); + if (res) { + int code = res->status; + switch(code) { + case 204: + case 200: + break; + default: + cout << "INFLUX TCP ADAPTER: Send points to influx: POST returned " << code << " - " << res->body << EOL << flush; + } + } else { + cerr << TAG << ": HTTP request failed (no response)" << endl; + } } catch (std::exception& e) { - OATPP_LOGE(TAG, "sending points: %s", e.what()); - } - - switch(code) { - case 204: - case 200: - break; - default: - cout << "INFLUX TCP ADAPTER: Send points to influx: POST returned " << code << " - " << desc->c_str() << EOL << flush; + cerr << TAG << ": sending points: " << e.what() << endl; } }); @@ -864,60 +782,50 @@ void InfluxTcpAdapter::sendPointsWithString(const std::string& content) { } -string InfluxTcpAdapter::encodeQuery(string queryString){ - std::string q = __url_encode(queryString); - return q; -} -json InfluxTcpAdapter::jsonFromResponse(const std::shared_ptr response) { +json InfluxTcpAdapter::jsonFromResponseBody(int statusCode, const std::string& body) { this->lastError = ""; json js = json::object(); - auto errCallback = _errCallback; - auto connection = this->conn; - - if (response == nullptr) { - return js; - } - - int code = response->getStatusCode(); - if (code == 200) { - // OATPP_LOGI(TAG, "Connected"); - std::string bodyStr = response->readBodyToString().getValue(""); - // OATPP_LOGD(TAG, "%s", bodyStr.c_str()); - if (!json::accept(bodyStr)){ - OATPP_LOGE(TAG, "JSON Parse Error: %s", bodyStr.c_str()); - this->lastError = string(bodyStr.c_str()); + if (statusCode == 200) { + if (!json::accept(body)){ + cerr << TAG << ": JSON Parse Error: " << body << endl; + this->lastError = body; return js; } else { - js = json::parse(bodyStr); + js = json::parse(body); return js; } } else { - auto err = string(response->readBodyToString().getValue("(no body content)").c_str()); - cerr << TAG << ": Connection Error: " << response->getStatusDescription()->c_str() << " - " << err << endl; - auto errjs = json::parse(err); - this->lastError = errjs["error"]; + cerr << TAG << ": Connection Error: HTTP " << statusCode << " - " << body << endl; + if (json::accept(body)) { + auto errjs = json::parse(body); + if (errjs.contains("error")) { + this->lastError = errjs["error"].get(); + } + } else { + this->lastError = body; + } return js; } } -vector InfluxTcpAdapter::__pointsSingle(json& json) { +vector InfluxTcpAdapter::__pointsSingle(json& json) { auto multi = __pointsFromJson(json); if (multi.size() > 0) { return multi.begin()->second; } else { - return vector(); + return vector(); } } -map > InfluxTcpAdapter::__pointsFromJson(json& json) { +map > InfluxTcpAdapter::__pointsFromJson(json& json) { - map > out; + map > out; // check for correct response format: if (!json.is_object() || @@ -934,7 +842,7 @@ map > InfluxTcpAdapter::__pointsFromJson(json& json) { if ( !statement.is_object() || !statement.contains(kSERIES) ) { if (statement.contains("error")) { - OATPP_LOGE(InfluxTcpAdapter::TAG, "Error in json decoding: %s", statement["error"].dump().c_str()); + cerr << TAG << ": Error in json decoding: " << statement["error"].dump() << endl; } continue; } @@ -943,25 +851,23 @@ map > InfluxTcpAdapter::__pointsFromJson(json& json) { // assemble the proper identifier for this series MetricInfo metric(""); if (series.count("name") == 0) { - OATPP_LOGE(InfluxTcpAdapter::TAG, "Influx returned malformed response. No \"name\" property in series."); - OATPP_LOGI(InfluxTcpAdapter::TAG, "Series format returned: %s", series.dump().c_str()); + cerr << TAG << ": Influx returned malformed response. No \"name\" property in series." << endl; + cerr << TAG << ": Series format returned: " << series.dump() << endl; } - metric.measurement = series.at("name"); + metric.measurement = series.at("name").get(); if (series.contains("tags")) { auto tagsObj = series.at("tags"); - json::iterator tagsIter = tagsObj.begin(); - while (tagsIter != tagsObj.end()) { - auto key = tagsIter.key(); - string value = tagsIter.value(); + for (auto it = tagsObj.begin(); it != tagsObj.end(); ++it) { + auto key = it.key(); + string value = it.value(); if (value != "") { metric.tags[key] = value; } - ++tagsIter; } Units units = TSF_DIMENSIONLESS; if (metric.tags.count("units") == 0) { - OATPP_LOGE(InfluxTcpAdapter::TAG, "Influx returned malformed response. No \"units\" property in tag list."); - OATPP_LOGI(InfluxTcpAdapter::TAG, "Tags object format returned: %s", tagsObj.dump().c_str()); + cerr << TAG << ": Influx returned malformed response. No \"units\" property in tag list." << endl; + cerr << TAG << ": Tags object format returned: " << tagsObj.dump() << endl; } else { units = Units::unitOfType(metric.tags.at("units")); @@ -972,8 +878,8 @@ map > InfluxTcpAdapter::__pointsFromJson(json& json) { map columnMap; if (series.count("columns") == 0) { - OATPP_LOGE(InfluxTcpAdapter::TAG, "Influx returned malformed response. No \"columns\" property in series."); - OATPP_LOGI(InfluxTcpAdapter::TAG, "Series format returned: %s", series.dump().c_str()); + cerr << TAG << ": Influx returned malformed response. No \"columns\" property in series." << endl; + cerr << TAG << ": Series format returned: " << series.dump() << endl; } auto cols = series.at("columns"); for (int i = 0; i < cols.size(); ++i) { @@ -1000,8 +906,8 @@ map > InfluxTcpAdapter::__pointsFromJson(json& json) { confidenceIndex = columnMap.count("confidence") > 0 ? columnMap["confidence"] : -1; if (series.count("values") == 0) { - OATPP_LOGE(InfluxTcpAdapter::TAG, "Influx returned malformed response. No \"values\" property in series."); - OATPP_LOGI(InfluxTcpAdapter::TAG, "Series format returned: %s", series.dump().c_str()); + cerr << TAG << ": Influx returned malformed response. No \"values\" property in series." << endl; + cerr << TAG << ": Series format returned: " << series.dump() << endl; } const auto &values = series.at("values"); @@ -1011,7 +917,7 @@ map > InfluxTcpAdapter::__pointsFromJson(json& json) { } if (out.count(properId) == 0) { - out[properId] = vector(); + out[properId] = vector(); } auto pointVec = &(out.at(properId)); @@ -1025,27 +931,27 @@ map > InfluxTcpAdapter::__pointsFromJson(json& json) { const auto &row = rowV; // ensure that these values are non-null and castable if (row.at(timeIndex).is_null() || row.at(valueIndex).is_null() || !row.at(timeIndex).is_number() || !row.at(valueIndex).is_number()) { - OATPP_LOGW(InfluxTcpAdapter::TAG, "Influx returned malformed row: %s", row.dump().c_str()); + cerr << TAG << ": Influx returned malformed row: " << row.dump() << endl; continue; } time_t t = row.at(timeIndex); double v = row.at(valueIndex); - Point::PointQuality q = Point::opc_tsf_override; + TSF::Point::PointQuality q = TSF::Point::opc_tsf_override; if (qualityIndex > 0 && !row.at(qualityIndex).is_null()) { - q = (Point::PointQuality)(row.at(qualityIndex)); + q = (TSF::Point::PointQuality)(row.at(qualityIndex)); } double c = 0; if (confidenceIndex > 0 && !row.at(confidenceIndex).is_null()) { c = row.at(confidenceIndex); } - pointVec->push_back(Point(t,v,q,c)); + pointVec->push_back(TSF::Point(t,v,q,c)); } } } // sort these points for (auto &ts : out) { - std::sort(ts.second.begin(), ts.second.end(), Point::comparePointTime); + std::sort(ts.second.begin(), ts.second.end(), TSF::Point::comparePointTime); } @@ -1138,16 +1044,16 @@ IdentifierUnitsList InfluxUdpAdapter::idUnitsList() { } // READ -std::vector InfluxUdpAdapter::selectRange(const std::string& id, TimeRange range) { - return {Point()}; +std::vector InfluxUdpAdapter::selectRange(const std::string& id, TimeRange range) { + return {TSF::Point()}; } -Point InfluxUdpAdapter::selectNext(const std::string& id, time_t time, WhereClause q) { - return Point(); +TSF::Point InfluxUdpAdapter::selectNext(const std::string& id, time_t time, WhereClause q) { + return TSF::Point(); } -Point InfluxUdpAdapter::selectPrevious(const std::string& id, time_t time, WhereClause q) { - return Point(); +TSF::Point InfluxUdpAdapter::selectPrevious(const std::string& id, time_t time, WhereClause q) { + return TSF::Point(); } diff --git a/src/InfluxAdapter.h b/src/InfluxAdapter.h index 2adaff2c..99fc55d7 100644 --- a/src/InfluxAdapter.h +++ b/src/InfluxAdapter.h @@ -4,26 +4,12 @@ #include #include #include +#include -#include "oatpp/web/client/HttpRequestExecutor.hpp" -#include "oatpp/network/tcp/client/ConnectionProvider.hpp" -#include "oatpp/network/ConnectionPool.hpp" -#include "oatpp-openssl/client/ConnectionProvider.hpp" -#include "oatpp-openssl/Config.hpp" -#include "oatpp/parser/json/mapping/ObjectMapper.hpp" -#include "oatpp/core/async/Executor.hpp" -#include "oatpp/network/virtual_/client/ConnectionProvider.hpp" -#include "oatpp/network/virtual_/server/ConnectionProvider.hpp" -#include "oatpp/network/virtual_/Interface.hpp" -#include "oatpp/network/monitor/ConnectionMonitor.hpp" -#include "oatpp/network/monitor/ConnectionInactivityChecker.hpp" -#include "oatpp/network/monitor/ConnectionMaxAgeChecker.hpp" -#include "oatpp/network/ConnectionPool.hpp" - +#include "httplib.h" #include "nlohmann/json.hpp" #include "DbAdapter.h" -#include "InfluxClient.hpp" namespace TSF { class InfluxAdapter : public DbAdapter { @@ -86,7 +72,6 @@ namespace TSF { public: InfluxTcpAdapter( errCallback_t cb ); - InfluxTcpAdapter(errCallback_t cb, std::shared_ptr restClient ); ~InfluxTcpAdapter(); const adapterOptions options() const; @@ -99,7 +84,6 @@ namespace TSF { Point selectNext(const std::string& id, time_t time, WhereClause q = WhereClause()); Point selectPrevious(const std::string& id, time_t time, WhereClause q = WhereClause()); std::vector selectWithQuery(const std::string& query, TimeRange range); - std::shared_ptr _restClient; // PREFETCH @@ -114,8 +98,6 @@ namespace TSF { void sendPointsWithString(const std::string& content); std::string formatTimestamp(time_t t); - private: - typedef oatpp::web::protocol::http::incoming::Response Response; private: class Query { public: @@ -127,15 +109,15 @@ namespace TSF { std::shared_ptr _sendTask; constexpr static const char* TAG = "InfluxTCPAdapter"; - std::shared_ptr _objectMapper; - std::shared_ptr createExecutor(); + std::unique_ptr _httpClient; + void createHttpClient(); std::future sendPointsFuture; Query queryPartsFromMetricId(const std::string& name); - std::string encodeQuery(std::string queryString); - nlohmann::json jsonFromResponse(const std::shared_ptr response); - + nlohmann::json executeQuery(const std::string& query, const std::string& epoch = ""); + nlohmann::json jsonFromResponseBody(int statusCode, const std::string& body); + static std::map > __pointsFromJson(nlohmann::json& json); static std::vector __pointsSingle(nlohmann::json& json); }; diff --git a/src/InfluxClient.hpp b/src/InfluxClient.hpp deleted file mode 100644 index 5852ba90..00000000 --- a/src/InfluxClient.hpp +++ /dev/null @@ -1,44 +0,0 @@ -// -// InfluxClient.hpp -// tsflib -// -// Created by Devine, Cameron - Xylem on 2/14/22. -// - -#ifndef InfluxClient_h -#define InfluxClient_h - -#include "oatpp/web/client/ApiClient.hpp" -#include "oatpp/core/macro/codegen.hpp" - -#include OATPP_CODEGEN_BEGIN(ApiClient) - -class InfluxClient : public oatpp::web::client::ApiClient { - - -private: - constexpr static const char* HTTP_GET = "GET"; - constexpr static const char* HTTP_POST = "POST"; - constexpr static const char* HTTP_PUT = "PUT"; - constexpr static const char* HTTP_DELETE = "DELETE"; -public: - API_CLIENT_INIT(InfluxClient) - - //----------------------------------------------------------------------------------------------- - // Synchronous calls - // - API_CALL(HTTP_GET, "query", doCreate, AUTHORIZATION_BASIC(String, authString), QUERY(String, q, "q")) - API_CALL(HTTP_GET, "query", doQuery, AUTHORIZATION_BASIC(String, authString), QUERY(String, db, "db"), QUERY(String, q, "q")) - API_CALL(HTTP_GET, "query", doQueryWithTimePrecision, AUTHORIZATION_BASIC(String, authString), QUERY(String, db, "db"), QUERY(String, q, "q"), QUERY(String, epoch, "epoch")) - API_CALL(HTTP_POST, "query", removeRecord, AUTHORIZATION_BASIC(String, authString), QUERY(String, q, "q")) - API_CALL(HTTP_POST, "write", sendPoints, AUTHORIZATION_BASIC(String, authString), HEADER(String, contentEncoding, "Content-Encoding"), QUERY(String, db, "db"), QUERY(String, precision, "precision"), BODY_STRING(String, data)) - - - // FOR TESTING - API_CALL("GET", "/", getRoot) - // -#include OATPP_CODEGEN_END(ApiClient) -}; - - -#endif /* InfluxClient_h */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8679420c..d3afd207 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -2,11 +2,12 @@ add_executable( tsflib-test test_main.cpp test_units.cpp test_curve.cpp - test_record.cpp + test_clock.cpp + test_record.cpp test_filters.cpp ) -set_target_properties(tsflib-test PROPERTIES CXX_STANDARD 17) +set_target_properties(tsflib-test PROPERTIES CXX_STANDARD 23) target_include_directories(tsflib-test PRIVATE ${Boost_INCLUDE_DIRS}) target_link_libraries(tsflib-test ${tsf_lib_deps} tsflib ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}) diff --git a/test/MyClientTest.cpp b/test/MyClientTest.cpp deleted file mode 100644 index 5f91a827..00000000 --- a/test/MyClientTest.cpp +++ /dev/null @@ -1,69 +0,0 @@ -// -// MyClientTest.hpp -// tsf-tests -// -// Created by Devine, Cameron - Xylem on 2/22/22. -// -#include "MyClientTest.hpp" - -#include - -#include "oatpp-test/UnitTest.hpp" - -#include "TestController.h" -#include "TestClient.h" - -#include "ConcreteDbRecords.h" -#include "Components.hpp" - -#include "oatpp/web/client/HttpRequestExecutor.hpp" - -#include "oatpp-test/web/ClientServerTestRunner.hpp" - -void MyClientTest::onRun() { - - /* Register test components */ - Components component; - - /* Create client-server test runner */ - oatpp::test::web::ClientServerTestRunner runner; - - /* Add TestController endpoints to the router of the test server */ - runner.addController(std::make_shared()); - - /* Run test */ - runner.run([this, &runner] { - - /* Get client connection provider for Api Client */ - OATPP_COMPONENT(std::shared_ptr, clientConnectionProvider); - - /* Get object mapper component */ - OATPP_COMPONENT(std::shared_ptr, objectMapper); - - /* Create http request executor for Api Client */ - auto requestExecutor = oatpp::web::client::HttpRequestExecutor::createShared(clientConnectionProvider); - - /* Create Test API client */ - auto client = TestClient::createShared(requestExecutor, objectMapper); - - /* Call server API */ - /* Call root endpoint of MyController */ - auto response = client->getRoot(); - - /* Assert that server responds with 200 */ - OATPP_ASSERT(response->getStatusCode() == 0); - - /* Read response body as MessageDto */ - auto message = response->readBodyToDto>(objectMapper.get()); - - /* Assert that received message is as expected */ - OATPP_ASSERT(message); - OATPP_ASSERT(message->statusCode == 400); - OATPP_ASSERT(message->message == "Hello World!"); - - }, std::chrono::minutes(10) /* test timeout */); - - /* wait all server threads finished */ - std::this_thread::sleep_for(std::chrono::seconds(1)); - -} diff --git a/test/MyClientTest.hpp b/test/MyClientTest.hpp deleted file mode 100644 index b2e346e7..00000000 --- a/test/MyClientTest.hpp +++ /dev/null @@ -1,22 +0,0 @@ -// -// MyClientTest.hpp -// tsf-tests -// -// Created by Devine, Cameron - Xylem on 2/22/22. -// - -#ifndef MyClientTest_hpp -#define MyClientTest_hpp - - -#include "oatpp-test/UnitTest.hpp" - -class MyClientTest : public oatpp::test::UnitTest { - public: - - MyClientTest() : UnitTest("TEST[MyClientTest]"){} - void onRun() override; - - }; - -#endif /* MyClientTest_hpp */ diff --git a/test/TestClient.h b/test/TestClient.h deleted file mode 100644 index f80e44d9..00000000 --- a/test/TestClient.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// TestClient.h -// tsf-tests -// -// Created by Devine, Cameron - Xylem on 2/22/22. -// - -#ifndef TestClient_h -#define TestClient_h - -#include "oatpp/web/client/ApiClient.hpp" -#include "oatpp/core/macro/codegen.hpp" - -/* Begin Api Client code generation */ -#include OATPP_CODEGEN_BEGIN(ApiClient) - -/** - * Test API client. - * Use this client to call application APIs. - */ -class TestClient : public oatpp::web::client::ApiClient { - - API_CLIENT_INIT(TestClient) - - API_CALL("GET", "/", getRoot) - - // TODO - add more client API calls here - -}; - -/* End Api Client code generation */ -#include OATPP_CODEGEN_END(ApiClient) - -#endif /* TestClient_h */ diff --git a/test/TestController.h b/test/TestController.h deleted file mode 100644 index 4e1a8514..00000000 --- a/test/TestController.h +++ /dev/null @@ -1,93 +0,0 @@ -// -// TestController.h -// tsf-tests -// -// Created by Devine, Cameron - Xylem on 2/22/22. -// - -#ifndef TestController_h -#define TestController_h - -#include "oatpp/web/server/api/ApiController.hpp" -#include "oatpp/core/macro/codegen.hpp" -#include "oatpp/core/macro/component.hpp" -#include "oatpp/core/macro/codegen.hpp" -#include "oatpp/core/Types.hpp" -#include -#include OATPP_CODEGEN_BEGIN(DTO) - -/** - * Data Transfer Object. Object containing fields only. - * Used in API for serialization/deserialization and validation - */ -class MyDto : public oatpp::DTO { - - DTO_INIT(MyDto, DTO) - - DTO_FIELD(Int32, statusCode); - DTO_FIELD(String, message); - -}; - -class ResultsDto : public oatpp::DTO { - DTO_INIT(ResultsDto, DTO) - - DTO_FIELD(Int32, statusCode); - DTO_FIELD(String, results); -}; - -#include OATPP_CODEGEN_END(DTO) - -#include OATPP_CODEGEN_BEGIN(ApiController) ///Begin Codegen - -/** - * Sample Api Controller. - */ -class TestController : public oatpp::web::server::api::ApiController { -public: - /** - * Constructor with object mapper. - * @param objectMapper - default object mapper used to serialize/deserialize DTOs. - */ - TestController(OATPP_COMPONENT(std::shared_ptr, objectMapper)) - : oatpp::web::server::api::ApiController(objectMapper) - { - setDefaultAuthorizationHandler(std::make_shared("my-realm")); - } -public: - - ENDPOINT("GET", "/", root) { - auto dto = MyDto::createShared(); - dto->statusCode = 200; - dto->message = "Hello World!"; - return createDtoResponse(Status::CODE_200, dto); - } - - ENDPOINT("GET", "/query", query, QUERIES(QueryParams, queryParams)) { - //OATPP_ASSERT_HTTP(authObject->userId == "USER" && authObject->password == "PASS", Status::CODE_401, "Unauthorized"); - - //Get the query parameter for basic testing - std::string q; - for(auto& param : queryParams.getAll()) { - std::string first(param.first.std_str()); - std::string second(param.second.std_str()); - if(first.compare("q") == 0){ - q = second; - } - } - String results; - //Build example responses for certain queries - if(q.compare("SHOW%20MEASUREMENTS%20LIMIT%201") == 0){ - results = "{\"results\":[{\"statement_id\":0,\"series\":[{\"name\":\"measurements\",\"columns\":[\"name\"],\"values\":[[\"Cluster1.WTR_CrkerSprgs_PRV_DisPres_P\"]]}]}]}"; - } else if(q.compare("show%20series") == 0 ){ - results = "{\"results\":[{\"statement_id\":0,\"series\":[{\"columns\":[\"key\"],\"values\":[[\"Cluster1.WTR_CrkerSprgs_PRV_DisPres_P,units=psi\"],[\"Cluster1.WTR_DryCreekRd_PRV_DisPres_P,units=psi\"],[\"Cluster1.WTR_DryForkRd_PRV_DisPres_P,units=psi\"],[\"Cluster1.WTR_EatonCrkRd_PRV_DisPres_P,units=psi\"],[\"concentration,tag=Cluster1.WTR_38thAve_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_8thAve_CHM_CL2ResHach_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_8thAve_CHM_CL2ResProm_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_Airport_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_BatteryLn_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_BenAllen_RES_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_BkChBsPrk_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_BkChPk_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_BluBeryHil_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_Bonnafield_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_Bordeaux_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_BrntHghlnd_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_Brookmont_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_BullRun_RES_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_CaneRidge_RES_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_CentFarms_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_ClfdlKnbhl_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_EstesRd_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_Fairmd_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_GenelleDr_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_GranyWhite_RES_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_GranyWhite_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_HardingPl_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_HarpthTrce_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_Hillview_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_Hillwood_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_HilsborPrk_RES_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_HilwoodPrk_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_HndRn_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_HuntCanRdg_WPS_CR_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_HuntCanRdg_WPS_HR_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_IntrchgCty_RES_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_JclynHolow_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_Joelton_RES_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_Kinhawk_RES_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_KrHaringtn_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_LaurelRidg_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_LoveCircle_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_MillsRd_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_NrthmbrLnd_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_Oakhill_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_Oakwood_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_Ocala_RES_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_OldHickory_RES_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_OldHickory_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_Omohundro_WPS_CL2ResEast_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_Omohundro_WPS_CL2ResWest_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_Otterwood_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_PorterRd_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_PowellAve_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_RiceRoad_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_RollFork_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_Rxborough2_RES_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_Rxborough_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_Shepardwod_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_SherwdFrst_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_Southerland_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_Stanford_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_SwissAve_RES_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_TmpsonLane_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_Treemont_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_TrntyHlApt_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_TyneValEst_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_UnionHill_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_Villacrest_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_VirginaAve_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_WarnerPark_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_WhitesCrk_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_WstMeadEst_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_WstMeadFrm_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_WstMeadHls_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_WstMeadPrk_WPS_CL2Res_P,units=mg/L\"],[\"concentration,tag=Cluster1.WTR_WvrlyBlmnt_WPS_CL2Res_P,units=mg/L\"],[\"flow,tag=Cluster1.WTR_38thAve_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_Airport_PRV_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_Airport_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_BatteryLn_HardingPl_WPS_FlwSum_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_BatteryLn_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_BkChBsPrk_WPS_FlwSum_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_BkChBsPrk_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_BkChPk_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_BluBeryHil_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_Bonnafield_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_Bordeaux_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_Brentwood_FLW_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_BrntHghlnd_RES_DmdFlw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_BrntHghlnd_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_Brookmont_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_CaneRidge_RES_DmdFlw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_CentFarms_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_ClfdlKnbhl_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_EstesRd_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_Fairmd_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_GenelleDr_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_GranyWhite_RES_DmdFlw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_GranyWhite_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_HardingPl_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_HarpthTrce_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_Hillview_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_Hillwood_WPS_FlwSum_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_Hillwood_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_HilwoodPrk_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_HndRn_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_HuntCanRdg_RES_DmdFlw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_HuntCanRdg_WPS_CR_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_HuntCanRdg_WPS_FlwSum_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_HuntCanRdg_WPS_HR_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_JclynHolow_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_KrHaringtn_WPS_FinFlw1_P,units=mgd\"],[\"flow,tag=Cluster1.WTR_KrHaringtn_WPS_FinFlw2_P,units=mgd\"],[\"flow,tag=Cluster1.WTR_KrHaringtn_WPS_Flw_P,units=mgd\"],[\"flow,tag=Cluster1.WTR_LaurelRidg_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_LoveCircle_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_MillsRd_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_NrthmbrLnd_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_Oakhill_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_Oakwood_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_OldHickory_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_Oman_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_Omohundro_WPS_BWFlw1_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_Omohundro_WPS_FinFlw1_P,units=mgd\"],[\"flow,tag=Cluster1.WTR_Omohundro_WPS_FinFlw2_P,units=mgd\"],[\"flow,tag=Cluster1.WTR_Omohundro_WPS_Flw_P,units=mgd\"],[\"flow,tag=Cluster1.WTR_Otterwood_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_PorterRd_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_PowellAve_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_RiceRoad_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_RollFork_RES_DmdFlw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_RollFork_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_Rxborough_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_Shepardwod_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_SherwdFrst_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_Southerland_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_Stanford_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_ThorntonGrv_PRV_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_TmpsonLane_EstesRd_WPS_FlwSum_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_TmpsonLane_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_Treemont_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_TrntyHlApt_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_TyneValEst_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_UnionHill_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_Villacrest_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_VirginaAve_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_WarnerPark_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_WhitesCrk_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_WstMeadEst_RES_DmdFlw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_WstMeadEst_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_WstMeadFrm_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_WstMeadHls_RES_DmdFlw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_WstMeadHls_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_WstMeadPrk_WPS_Flw_P,units=gpm\"],[\"flow,tag=Cluster1.WTR_WvrlyBlmnt_WPS_Flw_P,units=gpm\"],[\"level,tag=Cluster1.WTR_38thAve_WPS_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_8thAve_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_8thAve_RES_Res2Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_BenAllen_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_BkChBsPrk_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_Bordeaux_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_BrntHghlnd_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_BullRun_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_CaneRidge_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_GranyWhite_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_HarpthTrce_WPS_WP_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_HilsborPrk_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_IntrchgCty_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_Joelton_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_Kinhawk_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_KrHaringtn_WPS_ClrWl1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_KrHaringtn_WPS_ClrWl2Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_LaurelRidg_WPS_TVE_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_LoveCircle_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_MillsRd_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_NrthmbrLnd_WPS_HGP_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_Oakhill2_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_Oakhill_WPS_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_Ocala_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_OldHickory_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_Omohundro_WPS_BWRes1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_Omohundro_WPS_ClrWl1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_Omohundro_WPS_ClrWl2Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_Omohundro_WPS_ClrWl3Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_Omohundro_WPS_ClrWl4Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_RiceRoad_WPS_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_RollFork_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_Rxborough2_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_SwissAve_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_TrinityLne_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_UnionHill_WPS_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_WhitesCrk_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_WstMeadEst_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_WstMeadHls_RES_Res1Lvl_P,units=ft\"],[\"level,tag=Cluster1.WTR_WstMeadPrk_RES_Res1Lvl_P,units=ft\"],[\"ph,tag=Cluster1.WTR_PowellAve_WPS_H2OPH_P,units=dimensionless\"],[\"position,tag=Cluster1.WTR_8thAve_RES_EfVPos_P,units=%\"],[\"position,tag=Cluster1.WTR_8thAve_RES_IVPos_P,units=%\"],[\"position,tag=Cluster1.WTR_Oakhill_WPS_VPos_P,units=%\"],[\"pressure,tag=Cluster1.WTR_38thAve_WPS_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_38thAve_WPS_MainPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_38thAve_WPS_SucPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_Airport_PRV_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_Airport_PRV_InPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_Airport_WPS_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_Airport_WPS_SucPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_AmalieDr_PRV_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_BarnesRd_PRV_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_BatteryLn_WPS_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_BatteryLn_WPS_SucPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_BellRd_PRV_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_BenAllen_RES_MainPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_BkChBsPrk_WPS_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_BkChBsPrk_WPS_SucPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_BkChPk_WPS_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_BkChPk_WPS_SucPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_BluBeryHil_WPS_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_BluBeryHil_WPS_SucPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_Bonnafield_WPS_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_Bonnafield_WPS_SucPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_Bordeaux_WPS_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_Bordeaux_WPS_SucPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_BriarvleRd_PRV_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_BriarvleRd_PRV_InPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_BrileyPkwy_PRV_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_BrntHghlnd_WPS_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_BrntHghlnd_WPS_SucPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_BrntwoodSq_PRV_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_Brookmont_WPS_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_Brookmont_WPS_SucPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_BullRun_RES_MainPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_CampbellRd_PRV_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_CaneRidge_PRV_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_CaneRidge_PRV_InPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_CentFarms_WPS_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_CentFarms_WPS_SucPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_ChsapeakDr_PRV_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_ClfdlKnbhl_WPS_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_ClfdlKnbhl_WPS_SucPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_CloverGlen_PRV_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_CloverGlen_PRV_InPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_EstesRd_WPS_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_EstesRd_WPS_SucPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_Fairmd_WPS_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_Fairmd_WPS_SucPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_GenelleDr_WPS_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_GenelleDr_WPS_SucPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_GranyWhite_RES_MainPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_GranyWhite_WPS_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_GranyWhite_WPS_SucPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_HardingPl_WPS_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_HardingPl_WPS_SucPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_HarpthTrce_WPS_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_HarpthTrce_WPS_SucPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_HearthtnLn_PRV_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_Hillview_WPS_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_Hillview_WPS_SucPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_Hillwood_WPS_DisPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_Hillwood_WPS_SucPres_P,units=psi\"],[\"pressure,tag=Cluster1.WTR_HilsborPrk_RES_MainPres_P,units=psi\"],[\"speed,tag=Cluster1.WTR_Hillwood_WPS_Pmp_SPEED,units=%\"],[\"speed,tag=Cluster1.WTR_HilwoodPrk_WPS_Pmp_SPEED,units=%\"],[\"speed,tag=Cluster1.WTR_HndRn_WPS_Pmp_SPEED,units=%\"],[\"speed,tag=Cluster1.WTR_HuntCanRdg_WPS_CR_Pmp_SPEED,units=%\"],[\"speed,tag=Cluster1.WTR_HuntCanRdg_WPS_HR_Pmp_SPEED,units=%\"],[\"speed,tag=Cluster1.WTR_JclynHolow_WPS_Pmp_SPEED,units=%\"],[\"speed,tag=Cluster1.WTR_KrHaringtn_WPS_Pmp1HS_Speed,units=%\"],[\"speed,tag=Cluster1.WTR_KrHaringtn_WPS_Pmp2HS_Speed,units=%\"],[\"speed,tag=Cluster1.WTR_KrHaringtn_WPS_Pmp3HS_Speed,units=%\"],[\"speed,tag=Cluster1.WTR_KrHaringtn_WPS_Pmp4HS_Speed,units=%\"],[\"speed,tag=Cluster1.WTR_KrHaringtn_WPS_Pmp_SPEED,units=%\"],[\"speed,tag=Cluster1.WTR_LaurelRidg_WPS_Pmp_SPEED,units=%\"],[\"speed,tag=Cluster1.WTR_LoveCircle_WPS_Pmp_SPEED,units=%\"],[\"speed,tag=Cluster1.WTR_MillsRd_WPS_Pmp_SPEED,units=%\"],[\"speed,tag=Cluster1.WTR_NrthmbrLnd_WPS_Pmp_SPEED,units=%\"],[\"speed,tag=Cluster1.WTR_Oakhill_WPS_Pmp_SPEED,units=%\"],[\"speed,tag=Cluster1.WTR_Oakwood_WPS_Pmp_SPEED,units=%\"],[\"speed,tag=Cluster1.WTR_OldHickory_WPS_Pmp_SPEED,units=%\"],[\"speed,tag=Cluster1.WTR_WvrlyBlmnt_WPS_Pmp_SPEED,units=%\"],[\"status,tag=Cluster1.WTR_38thAve_WPS_Gen_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_38thAve_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_38thAve_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_38thAve_WPS_Pmp3_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_38thAve_WPS_Pmp4_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_38thAve_WPS_RES_V1_OP,units=dimensionless\"],[\"status,tag=Cluster1.WTR_38thAve_WPS_RES_V1_STATVLV,units=dimensionless\"],[\"status,tag=Cluster1.WTR_8thAve_RES_EstCPmp_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_8thAve_RES_EstUPmp_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Airport_WPS_Gen_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Airport_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Airport_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Airport_WPS_Pmp3_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Airport_WPS_Pmp4_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Airport_WPS_SurgV_OP,units=dimensionless\"],[\"status,tag=Cluster1.WTR_AshlandCtyHw_VLV_V1_OP,units=dimensionless\"],[\"status,tag=Cluster1.WTR_AshlandCtyHw_VLV_V1_STATVLV,units=dimensionless\"],[\"status,tag=Cluster1.WTR_BatteryLn_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_BatteryLn_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_BatteryLn_WPS_Pmp3_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_BatteryLn_WPS_Pmp4_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_BenAllen_RES_V1_OP,units=dimensionless\"],[\"status,tag=Cluster1.WTR_BenAllen_RES_V1_STATVLV,units=dimensionless\"],[\"status,tag=Cluster1.WTR_BkChBsPrk_RES_V1_OP,units=dimensionless\"],[\"status,tag=Cluster1.WTR_BkChBsPrk_RES_V1_STATVLV,units=dimensionless\"],[\"status,tag=Cluster1.WTR_BkChBsPrk_WPS_Gen_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_BkChBsPrk_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_BkChBsPrk_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_BkChPk_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_BkChPk_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_BkChPk_WPS_Pmp3_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_BluBeryHil_WPS_Gen_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_BluBeryHil_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_BluBeryHil_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Bonnafield_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Bordeaux_RES_V1_OP,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Bordeaux_RES_V1_STATVLV,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Bordeaux_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Bordeaux_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Bordeaux_WPS_Pmp3_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_BrntHghlnd_RES_V1_OP,units=dimensionless\"],[\"status,tag=Cluster1.WTR_BrntHghlnd_RES_V1_STATVLV,units=dimensionless\"],[\"status,tag=Cluster1.WTR_BrntHghlnd_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_BrntHghlnd_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Brookmont_WPS_Gen_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Brookmont_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Brookmont_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Brookmont_WPS_Pmp3_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_BullRun_RES_V1_OP,units=dimensionless\"],[\"status,tag=Cluster1.WTR_BullRun_RES_V1_STATVLV,units=dimensionless\"],[\"status,tag=Cluster1.WTR_CaneRidge_RES_V1_OP,units=dimensionless\"],[\"status,tag=Cluster1.WTR_CaneRidge_RES_V1_STATVLV,units=dimensionless\"],[\"status,tag=Cluster1.WTR_CentFarms_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_CentFarms_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_CentFarms_WPS_Pmp3_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_CentFarms_WPS_Pmp4_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_ClfdlKnbhl_WPS_Gen_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_ClfdlKnbhl_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_ClfdlKnbhl_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_EstesRd_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_EstesRd_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Fairmd_WPS_Gen_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Fairmd_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Fairmd_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Fairmd_WPS_Pmp3_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_GenelleDr_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_GranyWhite_RES_V1_OP,units=dimensionless\"],[\"status,tag=Cluster1.WTR_GranyWhite_RES_V1_STATVLV,units=dimensionless\"],[\"status,tag=Cluster1.WTR_GranyWhite_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_GranyWhite_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_GranyWhite_WPS_Pmp3_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_GranyWhite_WPS_Pmp4_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_HardingPl_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_HardingPl_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_HardingPl_WPS_Pmp3_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_HarpthTrce_WPS_Gen_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_HarpthTrce_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_HarpthTrce_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_HarpthTrce_WPS_Pmp3_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_HarpthTrce_WPS_RES_V1_OP,units=dimensionless\"],[\"status,tag=Cluster1.WTR_HarpthTrce_WPS_RES_V1_STATVLV,units=dimensionless\"],[\"status,tag=Cluster1.WTR_HillsborRd_VLV_V1_OP,units=dimensionless\"],[\"status,tag=Cluster1.WTR_HillsborRd_VLV_V1_STATVLV,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Hillview_WPS_Gen_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Hillview_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Hillview_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Hillwood_WPS_Gen_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Hillwood_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Hillwood_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Hillwood_WPS_Pmp3_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_HilsborPrk_RES_V1_OP,units=dimensionless\"],[\"status,tag=Cluster1.WTR_HilsborPrk_RES_V1_STATVLV,units=dimensionless\"],[\"status,tag=Cluster1.WTR_HilwoodPrk_WPS_Gen_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_HilwoodPrk_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_HilwoodPrk_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_HndRn_WPS_Gen_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_HndRn_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_HndRn_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_HuntCanRdg_WPS_CR_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_HuntCanRdg_WPS_CR_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_HuntCanRdg_WPS_Gen_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_HuntCanRdg_WPS_HR_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Shepardwod_WPS_Pmp3_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_SherwdFrst_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_SherwdFrst_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_SherwdFrst_WPS_Pmp3_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Southerland_WPS_Gen_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Southerland_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Southerland_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Southerland_WPS_Pmp3_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Stanford_WPS_Gen_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Stanford_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Stanford_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_SwissAve_RES_V1_OP,units=dimensionless\"],[\"status,tag=Cluster1.WTR_SwissAve_RES_V1_STATVLV,units=dimensionless\"],[\"status,tag=Cluster1.WTR_TmpsonLane_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_TmpsonLane_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_TmpsonLane_WPS_Pmp3_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_TmpsonLane_WPS_Pmp4_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_TmpsonLane_WPS_SurgeVlv_OP,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Treemont_WPS_Gen_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Treemont_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Treemont_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Treemont_WPS_Pmp3_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_TrinityLne_RES_V1_OP,units=dimensionless\"],[\"status,tag=Cluster1.WTR_TrinityLne_RES_V1_STATVLV,units=dimensionless\"],[\"status,tag=Cluster1.WTR_TrntyHlApt_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_TrntyHlApt_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_TyneValEst_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_TyneValEst_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_UnionHill_WPS_Gen_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_UnionHill_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_UnionHill_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_UnionHill_WPS_Pmp3_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Villacrest_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Villacrest_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_Villacrest_WPS_Pmp3_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_VirginaAve_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_VirginaAve_WPS_Pmp2_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_VirginaAve_WPS_Pmp3_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_WarnerPark_WPS_Pmp1_RUN,units=dimensionless\"],[\"status,tag=Cluster1.WTR_WarnerPark_WPS_Pmp2_RUN,units=dimensionless\"],[\"temperature,tag=Cluster1.WTR_PowellAve_WPS_H2OTEMP_P,units=farenheit\"]]}]}]}"; - - } - return createResponse(Status::CODE_200, results); - } - -}; - -#include OATPP_CODEGEN_END(ApiController) ///End Codegen - -#endif /* TestController_h */ diff --git a/test/test_clock.cpp b/test/test_clock.cpp index 4156b0e3..2791b37d 100644 --- a/test/test_clock.cpp +++ b/test/test_clock.cpp @@ -1,9 +1,9 @@ #include "test_main.h" #include "Clock.h" +#include using namespace TSF; using namespace std; -using namespace nlohmann; //////////////////////// // clock serialization @@ -13,21 +13,23 @@ BOOST_AUTO_TEST_CASE(to_json_basic) { Clock c(3600, 60); c.setName("hourly"); - json j = c; + auto json = glz::write_json(c).value_or(""); + BOOST_CHECK(!json.empty()); - BOOST_CHECK_EQUAL(j["name"].get(), "hourly"); - BOOST_CHECK_EQUAL(j["period"].get(), 3600); - BOOST_CHECK_EQUAL(j["offset"].get(), 60); + Clock parsed; + auto err = glz::read_json(parsed, json); + BOOST_CHECK(!err); + BOOST_CHECK_EQUAL(parsed.name(), "hourly"); + BOOST_CHECK_EQUAL(parsed.period(), 3600); + BOOST_CHECK_EQUAL(parsed.start(), 60); } BOOST_AUTO_TEST_CASE(from_json_basic) { - json j = { - {"name", "five_min"}, - {"period", 300}, - {"offset", 0} - }; + string json = R"({"name":"five_min","period":300,"offset":0})"; - Clock c = j.get(); + Clock c; + auto err = glz::read_json(c, json); + BOOST_CHECK(!err); BOOST_CHECK_EQUAL(c.name(), "five_min"); BOOST_CHECK_EQUAL(c.period(), 300); @@ -39,8 +41,11 @@ BOOST_AUTO_TEST_CASE(roundtrip) { original.setName("quarter_hour"); // serialize then deserialize - json j = original; - Clock restored = j.get(); + auto json = glz::write_json(original).value_or(""); + + Clock restored; + auto err = glz::read_json(restored, json); + BOOST_CHECK(!err); BOOST_CHECK_EQUAL(restored.name(), original.name()); BOOST_CHECK_EQUAL(restored.period(), original.period()); @@ -53,8 +58,11 @@ BOOST_AUTO_TEST_CASE(roundtrip_preserves_isRegular) { Clock c(7200, 0); c.setName("two_hour"); - json j = c; - Clock restored = j.get(); + auto json = glz::write_json(c).value_or(""); + + Clock restored; + auto err = glz::read_json(restored, json); + BOOST_CHECK(!err); // isValid depends on _isRegular being set correctly via setPeriod BOOST_CHECK_EQUAL(restored.isValid(0), true); @@ -65,62 +73,51 @@ BOOST_AUTO_TEST_CASE(roundtrip_preserves_isRegular) { BOOST_AUTO_TEST_CASE(to_json_default_clock) { Clock c; // defaults: period=3600, start=0 - json j = c; + auto json = glz::write_json(c).value_or(""); + BOOST_CHECK(!json.empty()); - BOOST_CHECK_EQUAL(j["name"].get(), ""); - BOOST_CHECK_EQUAL(j["period"].get(), 3600); - BOOST_CHECK_EQUAL(j["offset"].get(), 0); + Clock parsed; + auto err = glz::read_json(parsed, json); + BOOST_CHECK(!err); + BOOST_CHECK_EQUAL(parsed.name(), ""); + BOOST_CHECK_EQUAL(parsed.period(), 3600); + BOOST_CHECK_EQUAL(parsed.start(), 0); } -BOOST_AUTO_TEST_CASE(from_json_missing_key_throws) { - json j = { - {"name", "bad"}, - {"period", 300} - // missing "offset" - }; +BOOST_AUTO_TEST_CASE(from_json_missing_key_uses_default) { + // glaze uses default-initialized values for missing keys (no throw) + string json = R"({"name":"bad","period":300})"; - BOOST_CHECK_THROW(j.get(), json::out_of_range); + Clock c; + auto err = glz::read_json(c, json); + BOOST_CHECK(!err); + BOOST_CHECK_EQUAL(c.start(), 0); // default-initialized } BOOST_AUTO_TEST_CASE(to_json_zero_period) { Clock c(0, 0); c.setName("irregular"); - json j = c; - - BOOST_CHECK_EQUAL(j["period"].get(), 0); + auto json = glz::write_json(c).value_or(""); + Clock parsed; + auto err = glz::read_json(parsed, json); + BOOST_CHECK(!err); + BOOST_CHECK_EQUAL(parsed.period(), 0); } BOOST_AUTO_TEST_CASE(to_json_only_contains_expected_keys) { Clock c(3600, 0); c.setName("test"); - json j = c; + auto json = glz::write_json(c).value_or(""); - BOOST_CHECK_EQUAL(j.size(), 3); - BOOST_CHECK(j.contains("name")); - BOOST_CHECK(j.contains("period")); - BOOST_CHECK(j.contains("offset")); -} - -BOOST_AUTO_TEST_CASE(from_json_ignores_extra_keys) { - json j = { - {"name", "test"}, - {"period", 600}, - {"offset", 10}, - {"class", "clock"}, - {"self", {{"uid", 1}, {"ref", "clock"}}} - }; - - // should not throw — extra keys are ignored - Clock c = j.get(); - - BOOST_CHECK_EQUAL(c.name(), "test"); - BOOST_CHECK_EQUAL(c.period(), 600); - BOOST_CHECK_EQUAL(c.start(), 10); + BOOST_CHECK(!json.empty()); + // glaze produces: {"name":"test","period":3600,"offset":0} + // verify 3 key-value pairs by counting colons + auto count = std::count(json.begin(), json.end(), ':'); + BOOST_CHECK_EQUAL(count, 3); } BOOST_AUTO_TEST_SUITE_END() // clock_serialization ///////////////////////// - diff --git a/test/test_influx.cpp b/test/test_influx.cpp index 73eed6e4..aa74ee38 100644 --- a/test/test_influx.cpp +++ b/test/test_influx.cpp @@ -6,20 +6,9 @@ // #include "test_main.h" #include "ConcreteDbRecords.h" -#include "MyClientTest.hpp" -#include "oatpp-test/UnitTest.hpp" -#include "TestController.h" -#include "TestClient.h" -#include "InfluxClient.hpp" #include "InfluxAdapter.h" -#include "ConcreteDbRecords.h" -#include "Components.hpp" - -#include "oatpp/web/client/HttpRequestExecutor.hpp" - -#include "oatpp-test/web/ClientServerTestRunner.hpp" using namespace TSF; using namespace std; @@ -39,115 +28,6 @@ BOOST_AUTO_TEST_CASE(influx_basic) { BOOST_CHECK_EQUAL(record->name(), recordName); BOOST_CHECK_EQUAL(record->connectionString(), connection); - BOOST_CHECK_EQUAL(oatpp::base::Environment::getObjectsCount(), 0); } -// -//BOOST_AUTO_TEST_CASE(influx_client_test){ -// oatpp::base::Environment::init(); -// /* Register test components */ -// Components component; -// -// /* Create client-server test runner */ -// oatpp::test::web::ClientServerTestRunner runner; -// -// /* Add TestController endpoints to the router of the test server */ -// runner.addController(std::make_shared()); -// -// /* Run test */ -// runner.run([this, &runner] { -// /* Get client connection provider for Api Client */ -// OATPP_COMPONENT(std::shared_ptr, clientConnectionProvider); -// -// /* Get object mapper component */ -// //OATPP_COMPONENT(std::shared_ptr, objectMapper); -// auto objectMapper = oatpp::parser::json::mapping::ObjectMapper::createShared(); -// -// /* Create http request executor for Api Client */ -// auto requestExecutor = oatpp::web::client::HttpRequestExecutor::createShared(clientConnectionProvider); -// -// /* Create Test API client */ -// auto client = InfluxClient::createShared(requestExecutor, objectMapper); -// -// /* Call server API */ -// /* Call root endpoint of MyController */ -// auto response = client->getRoot(); -// -// /* Assert that server responds with 200 */ -// BOOST_CHECK_EQUAL(response->getStatusCode(), 200); -// -// /* Read response body as MessageDto */ -// auto message = response->readBodyToDto>(objectMapper.get()); -// -// /* Assert that received message is as expected */ -// //BOOST_ASSERT(message); -// const string msg("Hello World!"); -// BOOST_CHECK_EQUAL(message->statusCode,200); -// BOOST_CHECK_EQUAL(message->message.getValue(""), msg); -// -// //Check that the auth part works -// string q("SHOW%20MEASUREMENTS%20LIMIT%201"); -// response = client->doCreate("", q); -// BOOST_CHECK_EQUAL(response->getStatusCode(), 200); -// -// auto _errCB = [&](const std::string& msg)->void { -// string errorMessage = msg; -// }; -// InfluxTcpAdapter adapter(_errCB, client); -// adapter.doConnect(); -// -// //Authenticated -// //response = client->doQuery("USER:PASS", "db", q); -// //BOOST_CHECK_EQUAL(response->getStatusCode(), 200); -// -// }, std::chrono::minutes(10) /* test timeout */); -// -// /* wait all server threads finished */ -// std::this_thread::sleep_for(std::chrono::seconds(1)); -// -//} -// -//BOOST_AUTO_TEST_CASE(influx_id_list){ -// //oatpp::base::Environment::init(); -// /* Register test components */ -// Components component; -// -// /* Create client-server test runner */ -// oatpp::test::web::ClientServerTestRunner runner; -// -// /* Add TestController endpoints to the router of the test server */ -// runner.addController(std::make_shared()); -// -// /* Run test */ -// runner.run([this, &runner] { -// /* Get client connection provider for Api Client */ -// OATPP_COMPONENT(std::shared_ptr, clientConnectionProvider); -// -// /* Get object mapper component */ -// OATPP_COMPONENT(std::shared_ptr, objectMapper); -// -// /* Create http request executor for Api Client */ -// auto requestExecutor = oatpp::web::client::HttpRequestExecutor::createShared(clientConnectionProvider); -// -// /* Create Test API client */ -// auto client = InfluxClient::createShared(requestExecutor, objectMapper); -// -// -// auto _errCB = [&](const std::string& msg)->void { -// string errorMessage = msg; -// }; -// InfluxTcpAdapter adapter(_errCB, client); -// auto list = adapter.idUnitsList(); -// BOOST_CHECK_GT(list.count(), 0); -// -// //Authenticated -// //response = client->doQuery("USER:PASS", "db", q); -// //BOOST_CHECK_EQUAL(response->getStatusCode(), 200); -// -// }, std::chrono::minutes(2) /* test timeout */); -// -// /* wait all server threads finished */ -// std::this_thread::sleep_for(std::chrono::seconds(1)); -// -//} BOOST_AUTO_TEST_SUITE_END()