//////////////////////////////////////////////////////////////////////////////// /// @brief test sr AQL Datefunctions /// /// @file /// /// DISCLAIMER /// /// Copyright 2017 ArangoDB GmbH, Cologne, Germany /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// /// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Michael Hackstein /// @author Copyright 2018, ArangoDB GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// #include "catch.hpp" #include "fakeit.hpp" #include "Aql/AqlValue.h" #include "Aql/ExpressionContext.h" #include "Aql/Functions.h" #include "Basics/SmallVector.h" #include "Transaction/Methods.h" #include #include #include #include using namespace arangodb; using namespace arangodb::aql; namespace arangodb { namespace tests { namespace date_functions_aql { struct TestDateModifierFlagFactory { public: enum FLAGS { INVALID, MILLI, SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR }; static std::vector createAllFlags(FLAGS const& e) { switch (e) { case INVALID: return {"abc"}; case MILLI: return {"f" ,"millisecond", "milliseconds", "MiLLiSeCOnd"}; case SECOND: return {"s" ,"second", "seconds", "SeCoNd"}; case MINUTE: return {"i" ,"minute", "minutes", "MiNutEs"}; case HOUR: return {"h" ,"hour", "hours", "HoUr"}; case DAY: return {"d" ,"day", "days", "daYs"}; case WEEK: return {"w" ,"week", "weeks", "WeEkS"}; case MONTH: return {"m" ,"month", "months", "mOnTHs"}; case YEAR: return {"y" ,"year", "years", "yeArS"}; } return {"abc"}; } static std::string createFlag(FLAGS const&e) { switch (e) { case INVALID: return "abc"; case MILLI: return "f"; case SECOND: return "s"; case MINUTE: return "i"; case HOUR: return "h"; case DAY: return "d"; case WEEK: return "w"; case MONTH: return "m"; case YEAR: return "y"; } } }; namespace is_datestring { struct TestDate { public: TestDate(std::string const json, bool v) : _date(nullptr), _isValid(v) { // Make sure to only insert valid JSON. // We are not testing the parser here. _date = arangodb::velocypack::Parser::fromJson(json); } std::string const testName() const { return _date->toJson() + " => " + (_isValid ? "true" : "false"); } void buildParams(VPackFunctionParameters& input) const { input.emplace_back(_date.get()); } void validateResult(AqlValue const& result) const { REQUIRE(result.isBoolean()); REQUIRE(result.toBoolean() == _isValid); } private: std::shared_ptr _date; bool _isValid; }; SCENARIO("Testing IS_DATESTRING", "[AQL][DATE]") { fakeit::Mock expressionContextMock; ExpressionContext& expressionContext = expressionContextMock.get(); fakeit::Mock trxMock; transaction::Methods& trx = trxMock.get(); GIVEN("the non error case") { std::vector testees = { #include "IS_DATESTRING.testcases" }; for (auto const& testee : testees) { THEN("Validating: " + testee.testName()) { SmallVector::allocator_type::arena_type arena; SmallVector params{arena}; testee.buildParams(params); AqlValue res = Functions::IsDatestring(&expressionContext, &trx, params); testee.validateResult(res); // Free input parameters for (auto& it : params) { it.destroy(); } } } } } } // is_datestring namespace date_compare { struct TestDate { public: TestDate(std::vector const args, bool v) : _isValid(v) { _argBuilder.openArray(); for (auto const& it : args) { _argBuilder.add(VPackValue(it)); } _argBuilder.close(); } std::string const testName() const { return "Input: " + _argBuilder.toJson() + " => " + (_isValid ? "true" : "false"); } void buildParams(VPackFunctionParameters& input) const { for (auto const& it : VPackArrayIterator(_argBuilder.slice())) { input.emplace_back(it); } } void validateResult(AqlValue const& result) const { REQUIRE(result.isBoolean()); REQUIRE(result.toBoolean() == _isValid); } private: arangodb::velocypack::Builder _argBuilder; bool _isValid; }; SCENARIO("Testing DATE_COMPARE", "[AQL][DATE]") { fakeit::Mock expressionContextMock; ExpressionContext& expressionContext = expressionContextMock.get(); fakeit::Mock trxMock; transaction::Methods& trx = trxMock.get(); GIVEN("the non error case") { std::vector testees = { #include "DATE_COMPARE.testcases" }; for (auto const& testee : testees) { THEN("Validating: " + testee.testName()) { SmallVector::allocator_type::arena_type arena; SmallVector params{arena}; testee.buildParams(params); AqlValue res = Functions::DateCompare(&expressionContext, &trx, params); testee.validateResult(res); // Free input parameters for (auto& it : params) { it.destroy(); } } } } } } // date_compare namespace date_diff { SCENARIO("Testing DATE_DIFF", "[AQL][DATE]") { fakeit::Mock expressionContextMock; ExpressionContext& expressionContext = expressionContextMock.get(); fakeit::Mock trxMock; transaction::Methods& trx = trxMock.get(); WHEN("Checking all modifier Flags") { // These dates differ by: // 1 year // 2 months // 1 week // 12 days // 4 hours // 5 minutes // 6 seconds // 123 milliseconds std::string const earlierDate = "2000-04-01T02:48:42.123"; std::string const laterDate = "2001-06-13T06:53:48.246"; // Exact milisecond difference double dateDiffMillis = 37857906123; // Average number of days per month in the given dates double avgDaysPerMonth = 31*8+30*5+28; avgDaysPerMonth /= 14; // We have 14 months SmallVector::allocator_type::arena_type arena; SmallVector params{arena}; VPackBuilder dateBuilder; dateBuilder.openArray(); dateBuilder.add(VPackValue(earlierDate)); dateBuilder.add(VPackValue(laterDate)); dateBuilder.close(); VPackBuilder flagBuilder; VPackBuilder switchBuilder; auto testCombinations = [&](std::string const& f, double expected) -> void { double eps = 0.05; params.clear(); flagBuilder.clear(); flagBuilder.add(VPackValue(f)); WHEN("using " + earlierDate + ", " + laterDate + ", " + f) { params.emplace_back(dateBuilder.slice().at(0)); params.emplace_back(dateBuilder.slice().at(1)); params.emplace_back(flagBuilder.slice()); THEN("returning float") { switchBuilder.add(VPackValue(true)); params.emplace_back(switchBuilder.slice()); AqlValue res = Functions::DateDiff(&expressionContext, &trx, params); REQUIRE(res.isNumber()); double out = res.toDouble(&trx); REQUIRE(out >= expected - eps); REQUIRE(out <= expected + eps); } THEN("returning integer") { switchBuilder.add(VPackValue(false)); params.emplace_back(switchBuilder.slice()); AqlValue res = Functions::DateDiff(&expressionContext, &trx, params); REQUIRE(res.isNumber()); REQUIRE(res.toDouble(&trx) == std::round(expected)); } } WHEN("using " + laterDate + ", " + earlierDate + ", " + f) { params.emplace_back(dateBuilder.slice().at(1)); params.emplace_back(dateBuilder.slice().at(0)); params.emplace_back(flagBuilder.slice()); THEN("returning float") { switchBuilder.add(VPackValue(true)); params.emplace_back(switchBuilder.slice()); AqlValue res = Functions::DateDiff(&expressionContext, &trx, params); REQUIRE(res.isNumber()); double out = res.toDouble(&trx); REQUIRE(out >= -(expected + eps)); REQUIRE(out <= -(expected - eps)); } THEN("returning integer") { switchBuilder.add(VPackValue(false)); params.emplace_back(switchBuilder.slice()); AqlValue res = Functions::DateDiff(&expressionContext, &trx, params); REQUIRE(res.isNumber()); REQUIRE(res.toDouble(&trx) == -std::round(expected)); } } for (auto& it : params) { it.destroy(); } }; WHEN("checking Millis") { double expectedDiff = dateDiffMillis; auto allFlags = TestDateModifierFlagFactory::createAllFlags(TestDateModifierFlagFactory::FLAGS::MILLI); for (auto const& f : allFlags) { testCombinations(f, expectedDiff); } } WHEN("checking Seconds") { double expectedDiff = dateDiffMillis / 1000; auto allFlags = TestDateModifierFlagFactory::createAllFlags(TestDateModifierFlagFactory::FLAGS::SECOND); for (auto const& f : allFlags) { testCombinations(f, expectedDiff); } } WHEN("checking Minutes") { double expectedDiff = dateDiffMillis / (1000 * 60); auto allFlags = TestDateModifierFlagFactory::createAllFlags(TestDateModifierFlagFactory::FLAGS::MINUTE); for (auto const& f : allFlags) { testCombinations(f, expectedDiff); } } WHEN("checking Hours") { double expectedDiff = dateDiffMillis / (1000 * 60 * 60); auto allFlags = TestDateModifierFlagFactory::createAllFlags(TestDateModifierFlagFactory::FLAGS::HOUR); for (auto const& f : allFlags) { testCombinations(f, expectedDiff); } } WHEN("checking Days") { double expectedDiff = dateDiffMillis / (1000 * 60 * 60 * 24); auto allFlags = TestDateModifierFlagFactory::createAllFlags(TestDateModifierFlagFactory::FLAGS::DAY); for (auto const& f : allFlags) { testCombinations(f, expectedDiff); } } WHEN("checking Weeks") { double expectedDiff = dateDiffMillis / (1000 * 60 * 60 * 24 * 7); auto allFlags = TestDateModifierFlagFactory::createAllFlags(TestDateModifierFlagFactory::FLAGS::WEEK); for (auto const& f : allFlags) { testCombinations(f, expectedDiff); } } WHEN("checking Months") { double expectedDiff = dateDiffMillis / (1000 * 60 * 60 * 24) / avgDaysPerMonth; auto allFlags = TestDateModifierFlagFactory::createAllFlags(TestDateModifierFlagFactory::FLAGS::MONTH); for (auto const& f : allFlags) { testCombinations(f, expectedDiff); } } WHEN("checking Years") { double expectedDiff = dateDiffMillis / (1000 * 60 * 60 * 24) / 365; auto allFlags = TestDateModifierFlagFactory::createAllFlags(TestDateModifierFlagFactory::FLAGS::YEAR); for (auto const& f : allFlags) { testCombinations(f, expectedDiff); } } } WHEN("checking leap days") { // TODO! } } } // date_diff namespace date_subtract { struct TestDate { public: TestDate(std::string const& json, std::string const& v) : _input(nullptr), _result(v) { // Make sure to only insert valid JSON. // We are not testing the parser here. _input = arangodb::velocypack::Parser::fromJson(json); } std::string const testName() const { return _input->toJson() + " => " + _result; } void buildParams(VPackFunctionParameters& input) const { VPackSlice s = _input->slice(); for (auto const& it : VPackArrayIterator(s)) { input.emplace_back(it); } } void validateResult(AqlValue const& result) const { REQUIRE(result.isString()); auto res = result.slice(); std::string ref = res.copyString(); // Readability in test Tool REQUIRE(ref == _result); } private: std::shared_ptr _input; std::string const _result; }; SCENARIO("Testing DATE_SUBTRACT", "[AQL][DATE]") { fakeit::Mock expressionContextMock; ExpressionContext& expressionContext = expressionContextMock.get(); fakeit::Mock trxMock; transaction::Methods& trx = trxMock.get(); GIVEN("the non error case") { std::vector testees = { #include "DATE_SUBTRACT.testcases" }; for (auto const& testee : testees) { THEN("Validating: " + testee.testName()) { SmallVector::allocator_type::arena_type arena; SmallVector params{arena}; testee.buildParams(params); AqlValue res = Functions::DateSubtract(&expressionContext, &trx, params); testee.validateResult(res); res.destroy(); // Free input parameters for (auto& it : params) { it.destroy(); } } } } } } // date_subtract } // date_functions_aql } // tests } // arangodb