diff --git a/CMakeLists.txt b/CMakeLists.txt index 63168d55..55c86097 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,7 +36,7 @@ set(FAKER_SOURCES src/modules/phone/Phone.cpp src/common/LuhnCheck.cpp src/common/mappers/PrecisionMapper.cpp - ) + src/modules/system/System.cpp) set(FAKER_UT_SOURCES src/modules/book/BookTest.cpp @@ -58,7 +58,7 @@ set(FAKER_UT_SOURCES src/modules/helper/HelperTest.cpp src/common/LuhnCheckTest.cpp src/common/mappers/PrecisionMapperTest.cpp - ) + src/modules/system/SystemTest.cpp) add_library(${LIBRARY_NAME} ${FAKER_SOURCES}) diff --git a/externals/googletest b/externals/googletest index cc366710..01e18376 160000 --- a/externals/googletest +++ b/externals/googletest @@ -1 +1 @@ -Subproject commit cc366710bbf40a9816d47c35802d06dbaccb8792 +Subproject commit 01e18376efe643a82cff468734f87f8c60e314b6 diff --git a/include/faker-cxx/Datatype.h b/include/faker-cxx/Datatype.h index 302cd9bd..ed575b0c 100644 --- a/include/faker-cxx/Datatype.h +++ b/include/faker-cxx/Datatype.h @@ -1,4 +1,5 @@ #pragma once +#include namespace faker { @@ -15,5 +16,25 @@ class Datatype * @endcode */ static bool boolean(); + + /** + * @brief Returns a random boolean. + * **Note:** + * A probability of `0.75` results in `true` being returned `75%` of the calls; likewise `0.3` => `30%`. + * If the probability is `<= 0.0`, it will always return `false`. + * If the probability is `>= 1.0`, it will always return `true`. + * The probability is limited to two decimal places. + * + * @param probability The probability (`[0.00, 1.00]`) of returning `true`. + * + * @returns Boolean. + * + * @code + * Datatype::boolean() // "false" + * Datatype::boolean(0.9) // "true" + * Datatype::boolean(0.1) // "false" + * @endcode + */ + static bool boolean(double probability); }; } diff --git a/include/faker-cxx/Helper.h b/include/faker-cxx/Helper.h index 8249e112..af31e76c 100644 --- a/include/faker-cxx/Helper.h +++ b/include/faker-cxx/Helper.h @@ -6,10 +6,13 @@ #include #include #include +#include +#include #include "../../src/common/LuhnCheck.h" #include "../src/common/StringHelper.h" #include "Number.h" +#include "Datatype.h" namespace faker { @@ -118,6 +121,65 @@ class Helper */ static std::string regexpStyleStringParse(const std::string& input); + /** + * @brief Returns a random key from given object. + * + * @tparam T The type of the object to select from. + * + * @param object The object to be used. + * + * @throws If the given object is empty + * + * @return A random key from given object. + * + * @code + * std::unordered_map testMap = { + * {1, "one"}, + * {2, "two"}, + * {3, "three"} + * }; + * Helper::objectKey(testMap) // "2" + * @endcode + */ + template + static typename T::key_type objectKey(const T& object) + { + if (object.empty()) { + throw std::runtime_error("Object is empty."); + } + + std::vector keys; + for (const auto& entry : object) { + keys.push_back(entry.first); + } + + return arrayElement(keys); + } + + /** + * @brief Returns the result of the callback if the probability check was successful, otherwise empty string. +* + * + * @tparam TResult The type of result of the given callback. + * + * @param callback The callback to that will be invoked if the probability check was successful. + * @param options.probability The probability (`[0.00, 1.00]`) of the callback being invoked. Defaults to `0.5`. + * + * @return The result of the callback if the probability check was successful, otherwise empty string. + * + * @code + * Helper::maybe([]() { return "Hello World!"; }) // "" + * Helper::maybe([]() { return 42; }, 0.9) // "42" + * @endcode + */ + template + static TResult maybe(std::function callback, double probability = 0.5) { + if (Datatype::boolean(probability)) { + return callback(); + } + return TResult(); + } + private: static std::random_device randomDevice; static std::mt19937 pseudoRandomGenerator; diff --git a/include/faker-cxx/Internet.h b/include/faker-cxx/Internet.h index 1a0c1421..72c09c51 100644 --- a/include/faker-cxx/Internet.h +++ b/include/faker-cxx/Internet.h @@ -6,6 +6,7 @@ #include "types/EmojiType.h" #include "types/Ipv4Address.h" #include "types/Ipv4Class.h" +#include "faker-cxx/String.h" namespace faker { @@ -198,5 +199,18 @@ class Internet * @endcode */ static std::string ipv4(const IPv4Address& baseIpv4Address, const IPv4Address& generationMask); + + /** + * @brief Returns a generated random mac address. + * + * @param sep Separator to use. Defaults to ":". Also can be "-" or "". + * + * @return A generated random mac address. + * + * @code + * Internet::mac() // "2d:10:34:2f:ac:ac" + * @endcode + */ + static std::string mac(const std::string& sep = ":"); }; } diff --git a/include/faker-cxx/Number.h b/include/faker-cxx/Number.h index 49a0c8f2..75fc69e8 100644 --- a/include/faker-cxx/Number.h +++ b/include/faker-cxx/Number.h @@ -7,6 +7,8 @@ #include #include #include +#include +#include namespace faker { @@ -357,8 +359,26 @@ class Number return distribution(pseudoRandomGenerator); } + /** + * @brief Returns a lowercase hexadecimal number. + * + * @param min Optional parameter for lower bound of generated number. + * @param max Optional parameter for upper bound of generated number. + * + * @return A lowercase hexadecimal number. + * + * @example + * Number::hex() // "b" + * Number::hex(0, 255) // "9d" + * + */ + static std::string hex(std::optional min = std::nullopt, std::optional max = std::nullopt); + + private: static std::random_device randomDevice; static std::mt19937 pseudoRandomGenerator; + static std::string convertToHex(int number); + }; } diff --git a/include/faker-cxx/System.h b/include/faker-cxx/System.h new file mode 100644 index 00000000..5a66563d --- /dev/null +++ b/include/faker-cxx/System.h @@ -0,0 +1,217 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "../src/common/StringHelper.h" +#include "types/commonInterface.h" +#include "types/cronDayOfWeek.h" +#include "types/DirectoryPath.h" +#include "types/MimeTypes.h" +#include "types/CronOptions.h" +#include "types/FileOptions.h" +#include "types/NetworkInterfaceOptions.h" + +#include "Datatype.h" +#include "Helper.h" +#include "Internet.h" +#include "Number.h" +#include "String.h" +#include "Word.h" + +namespace faker +{ +class System +{ +public: + /** + * @brief Returns a random file name with extension. + * + * @param options An option struct. + * + * @returns Random file name with extension. + * + * @code + * System::fileName() // "injustice.mpeg" + * + * FileOptions options + * options.extensionCount = 3 + * System::fileName(options) // "transformation.wav.mpeg.mp4" + * + * options.extensionRange.min = 1; + * options.extensionRange.max = 3; + * System::fileName(options) // "sparkle.png.pdf" + * @endcode + */ + static std::string fileName(const FileOptions& options = {}); + + /** + * @brief Returns a file extension. + * + * @param mimeType The optional string to use. + * + * @returns A file extension. + * + * @code + * System::fileExt() // "wav" + * System::fileExt("application/pdf") // "pdf" + * @endcode + */ + static std::string fileExt(const std::optional& mimeType = std::nullopt); + + /** + * Returns a random file name with a given extension or a commonly used extension. + * + * @param ext Optional extension parameter. + * + * @returns A random file name with a given extension or a commonly used extension. + * + * @example + * System::commonFileName() // "dollar.jpg" + * System::commonFileName("txt") // "global_borders_wyoming.txt" + * + */ + static std::string commonFileName(const std::optional& ext = std::nullopt); + + /** + * Returns a commonly used file extension. + * + * @returns A commonly used file extension. + * + * @example + * System::commonFileExt() // "gif" + * + */ + static std::string commonFileExt(); + + /** + * Returns a mime-type. + * + * @returns A mime-type. + * + * @example + * System::mimeType() // "video/vnd.vivo" + * + */ + static std::string mimeType(); + + /** + * Returns a commonly used file type. + * + * @returns A commonly used file type. + * + * @example + * System::commonFileType() // "audio" + * + */ + static std::string commonFileType(); + + /** + * Returns a commonly used file type. + * + * @returns A commonly used file type. + * + * @example + * System::fileType() // "image" + * + */ + static std::string fileType(); + + /** + * Returns a directory path. + * + * @returns A directory path. + * + * @example + * System::directoryPath() // "/etc/mail" + * + */ + static std::string directoryPath(); + + /** + * Returns a file path. + * + * @returns A file path. + * + * @example + * System::filePath() // "/usr/local/src/money.dotx" + * + */ + static std::string filePath(); + + /** + * Returns a semantic version. + * + * @returns A semantic version. + * + * @example + * System::semver() // "1.1.2" + * + */ + static std::string semver(); + + /** + * Returns a random network interface. + * + * @param options The options to use. Defaults to an empty options structure @see NetworkInterfaceOptions.h. + * @param options.interfaceType The interface type. Can be one of `en`, `wl`, `ww`. + * @param options.interfaceSchema The interface schema. Can be one of `index`, `slot`, `mac`, `pci`. + * + * @returns A random network interface. + * + * @example + * System::networkInterface() // "enp2s7f8" + * + * NetworkInterfaceOptions options; + * options.interfaceType = "wl"; + * System::networkInterface(options) // "wlsf4d2" + * + * NetworkInterfaceOptions options; + * options.interfaceSchema = "mac"; + * System::networkInterface(options) // "enxd17705ed394f" + * + * NetworkInterfaceOptions options; + * options.interfaceType = "en"; + * options.interfaceSchema = "pci"; + * System::networkInterface(options) // "enp1s9f1d2" + * + */ + static std::string networkInterface(const std::optional& options = {}); + + /** + * Returns a random cron expression. + * + * @param options The options to use. Defaults to an empty options structure @see CronOptions.h. + * @param options.includeYear Whether to include a year in the generated expression. Defaults to `false`. + * @param options.includeNonStandard Whether to include a @yearly, @monthly, @daily, etc text labels in the generated expression. Defaults to `false`. + * + * @returns A random cron expression. + * + * @example + * system.cron() // "22 * ? * ?" + * + * CronOptions options + * options.includeYear = true + * std::string cronExpr = System::cron(options) // "16 14 * 11 2 2038" + * + * CronOptions options + * options.includeYear = false + * std::string cronExpr = System::cron(options) // "16 14 * 11 2" + * + * CronOptions options + * options.includeNonStandard = false + * std::string cronExpr = System::cron(options) // 34 2 ? 8 * + * + * CronOptions options + * options.includeNonStandard = true + * std::string cronExpr = System::cron(options) // "@reboot" + * + */ + static std::string cron(const CronOptions& options = {}); +}; +} diff --git a/include/faker-cxx/types/CronOptions.h b/include/faker-cxx/types/CronOptions.h new file mode 100644 index 00000000..15f68183 --- /dev/null +++ b/include/faker-cxx/types/CronOptions.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +namespace faker +{ +struct CronOptions { + bool includeYear = false; + bool includeNonStandard = false; +}; +} diff --git a/include/faker-cxx/types/DirectoryPath.h b/include/faker-cxx/types/DirectoryPath.h new file mode 100644 index 00000000..73e3256c --- /dev/null +++ b/include/faker-cxx/types/DirectoryPath.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include + +namespace faker +{ +const std::vector directoryPaths = { + "/etc/mail", + "/var/log", + "/usr/bin", + "/tmp", + "/usr/local/lib", + "/var/www/html", + "/opt/app", + "/home/user/documents", + "/usr/share/fonts", + "/var/cache/apt", + "/etc/nginx", + "/usr/local/bin", + "/var/tmp", + "/opt/data", + "/home/user/pictures", + "/usr/local/include", + "/var/www/cgi-bin", + "/etc/ssh", + "/usr/local/share", + "/var/spool/mail", + "/opt/logs", + "/home/user/music", + "/usr/local/libexec", + "/var/www/cgi-bin", + "/etc/ssl", + "/usr/local/var", + "/var/spool/cron", + "/opt/config", + "/home/user/videos", + "/usr/local/sbin", + "/var/www/docs", + "/etc/apache2", + "/usr/local/games", + "/var/run", + "/opt/bin", + "/home/user/downloads", + "/usr/local/man", + "/var/www/icons", + "/etc/mysql", + "/usr/local/src", + "/var/lock", + "/opt/scripts", + "/home/user/public_html", + "/usr/local/etc", + "/var/www/logs", + "/etc/httpd", + "/usr/local/share/man", + "/var/log/apache2", + "/opt/files", + "/home/user/backups" +}; +} \ No newline at end of file diff --git a/include/faker-cxx/types/FileOptions.h b/include/faker-cxx/types/FileOptions.h new file mode 100644 index 00000000..f8242f17 --- /dev/null +++ b/include/faker-cxx/types/FileOptions.h @@ -0,0 +1,12 @@ +#pragma once + +namespace faker +{ +struct FileOptions { + int extensionCount = 1; + struct { + int min = 1; + int max = 1; + } extensionRange; +}; +} \ No newline at end of file diff --git a/include/faker-cxx/types/MimeTypes.h b/include/faker-cxx/types/MimeTypes.h new file mode 100644 index 00000000..4d4418f2 --- /dev/null +++ b/include/faker-cxx/types/MimeTypes.h @@ -0,0 +1,89 @@ +#pragma once + +#include +#include + +namespace faker +{ +const std::vector mimeTypes = { + "application/atom+xml", + "application/font-woff", + "application/gzip", + "application/java-archive", + "application/javascript", + "application/json", + "application/ld+json", + "application/msword", + "application/octet-stream", + "application/ogg", + "application/pdf", + "application/rdf+xml", + "application/rtf", + "application/vnd.apple.mpegurl", + "application/vnd.ms-excel", + "application/vnd.ms-fontobject", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "application/x-7z-compressed", + "application/x-font-ttf", + "application/x-javascript", + "application/x-mpegURL", + "application/x-rar-compressed", + "application/x-shockwave-flash", + "application/x-tar", + "application/x-www-form-urlencoded", + "application/xhtml+xml", + "application/xml", + "application/zip", + "audio/aac", + "audio/midi", + "audio/ogg", + "audio/wav", + "audio/webm", + "audio/mpeg", + "font/woff", + "font/woff2", + "image/apng", + "image/bmp", + "image/gif", + "image/jpeg", + "image/png", + "image/svg+xml", + "image/tiff", + "image/webp", + "image/x-icon", + "multipart/form-data", + "text/calendar", + "text/css", + "text/csv", + "text/html", + "text/javascript", + "text/plain", + "text/xml", + "video/mp4", + "video/3gpp", + "video/3gpp2", + "video/mp2t", + "video/mpeg", + "video/ogg", + "video/webm", + "video/x-msvideo", + "video/x-flv" +}; + +const std::vector commonMimeTypes = { + "application/pdf", + "audio/mpeg", + "audio/wav", + "image/png", + "image/jpeg", + "image/gif", + "video/mp4", + "video/mpeg", + "text/html" +}; + +const std::vector commonFileTypes = {"video", "audio", "image", "text", "application"}; + +} diff --git a/include/faker-cxx/types/NetworkInterfaceOptions.h b/include/faker-cxx/types/NetworkInterfaceOptions.h new file mode 100644 index 00000000..2d1f135a --- /dev/null +++ b/include/faker-cxx/types/NetworkInterfaceOptions.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include +#include +#include + +namespace faker +{ +struct NetworkInterfaceOptions { + std::optional interfaceType; + std::optional interfaceSchema; +}; +} diff --git a/include/faker-cxx/types/commonInterface.h b/include/faker-cxx/types/commonInterface.h new file mode 100644 index 00000000..d39e842a --- /dev/null +++ b/include/faker-cxx/types/commonInterface.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +namespace faker +{ +const std::vector commonInterfaceTypes = {"en", "wl", "ww"}; +const std::unordered_map commonInterfaceSchemas = { + {"index", "o"}, + {"slot", "s"}, + {"mac", "x"}, + {"pci", "p"} +}; +} \ No newline at end of file diff --git a/include/faker-cxx/types/cronDayOfWeek.h b/include/faker-cxx/types/cronDayOfWeek.h new file mode 100644 index 00000000..b6509a27 --- /dev/null +++ b/include/faker-cxx/types/cronDayOfWeek.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include + +namespace faker +{ +const std::vector cronDayOfWeek = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"}; +} \ No newline at end of file diff --git a/src/modules/datatype/Datatype.cpp b/src/modules/datatype/Datatype.cpp index 653686aa..7a0b19ff 100644 --- a/src/modules/datatype/Datatype.cpp +++ b/src/modules/datatype/Datatype.cpp @@ -8,4 +8,23 @@ bool Datatype::boolean() { return Number::decimal(0.f, 1.f) > 0.5f; } + +bool Datatype::boolean(double probability) +{ + if(probability != NAN) + { + double prob = probability; + if (prob <= 0.f) + { + return false; + } + if (prob >= 1.f) + { + return true; + } + return Number::decimal(0.f, 1.f) < prob; + } + + return Number::decimal(0.f, 1.f) < 0.5f; +} } diff --git a/src/modules/datatype/DatatypeTest.cpp b/src/modules/datatype/DatatypeTest.cpp index af3d5730..dd1d5023 100644 --- a/src/modules/datatype/DatatypeTest.cpp +++ b/src/modules/datatype/DatatypeTest.cpp @@ -21,3 +21,18 @@ TEST_F(DatatypeTest, shouldGenerateBoolean) ASSERT_TRUE(std::any_of(booleanValues.begin(), booleanValues.end(), [boolean](bool booleanValue) { return boolean == booleanValue; })); } + +TEST_F(DatatypeTest, BooleanWithProbTest) +{ + bool result2 = Datatype::boolean(0.3); + EXPECT_TRUE(result2 || !result2); + + bool result3 = Datatype::boolean(0.8); + EXPECT_TRUE(result3 || !result3); + + bool result4 = Datatype::boolean(0.0); + EXPECT_FALSE(result4); + + bool result5 = Datatype::boolean(1.0); + EXPECT_TRUE(result5); +} diff --git a/src/modules/helper/HelperTest.cpp b/src/modules/helper/HelperTest.cpp index 7394b2ee..d85c2697 100644 --- a/src/modules/helper/HelperTest.cpp +++ b/src/modules/helper/HelperTest.cpp @@ -2,7 +2,9 @@ #include "gtest/gtest.h" #include #include - +#include +#include +#include "faker-cxx/String.h" using namespace faker; using namespace ::testing; @@ -54,4 +56,58 @@ TEST_F(HelperTest, ReplaceCreditCardSymbols) ASSERT_TRUE(result_custom[8] >= '0' && result_custom[8] <= '9'); ASSERT_TRUE(result_custom[9] >= '0' && result_custom[9] <= '9'); } + +TEST_F(HelperTest, ObjectKeyTest) +{ + std::unordered_map testMap = { + {1, "one"}, + {2, "two"}, + {3, "three"} + }; + + ASSERT_NO_THROW({ + int key = Helper::objectKey(testMap); + EXPECT_TRUE(testMap.find(key) != testMap.end()); + }); + + std::unordered_map emptyMap; + + ASSERT_THROW({ + Helper::objectKey(emptyMap); + }, std::runtime_error); +} + +TEST_F(HelperTest, MaybeString) +{ + double highProbability = 1; + auto result = Helper::maybe([]() { return "Hello World!"; }, highProbability); + EXPECT_EQ(result, "Hello World!"); + + double lowProbability = 0; + result = Helper::maybe([]() { return "Hello World!"; }, lowProbability); + EXPECT_EQ(result, ""); +} + +TEST_F(HelperTest, MaybeInt) +{ + double highProbability = 1; + auto result = Helper::maybe([]() { return 42; }, highProbability); + EXPECT_EQ(result, 42); + + double lowProbability = 0; + result = Helper::maybe([]() { return 42; }, lowProbability); + EXPECT_EQ(result, 0); +} + +TEST_F(HelperTest, MaybeDouble) +{ + double highProbability = 1; + auto result = Helper::maybe([]() { return 3.14; }, highProbability); + EXPECT_EQ(result, 3.14); + + // Test with low probability to ensure callback is not invoked + double lowProbability = 0; + result = Helper::maybe([]() { return 3.14; }, lowProbability); + EXPECT_EQ(result, 0.0); +} } \ No newline at end of file diff --git a/src/modules/internet/Internet.cpp b/src/modules/internet/Internet.cpp index a6f79f14..a3e09869 100644 --- a/src/modules/internet/Internet.cpp +++ b/src/modules/internet/Internet.cpp @@ -207,4 +207,24 @@ std::string Internet::ipv4(const IPv4Address& baseIpv4Address, const IPv4Address return std::format("{}.{}.{}.{}", sectors[0], sectors[1], sectors[2], sectors[3]); } +std::string Internet::mac(const std::string& sep) +{ + std::string mac; + std::string currentSep = sep; + std::vector acceptableSeparators = {":", "-", ""}; + + if (std::find(acceptableSeparators.begin(), acceptableSeparators.end(), currentSep) == acceptableSeparators.end()) + { + currentSep = ":"; + } + + for (int i = 0; i < 12; i++) + { + mac += Number::hex(); + if (i % 2 == 1 && i != 11) { + mac += currentSep; + } + } + return mac; +} } diff --git a/src/modules/internet/InternetTest.cpp b/src/modules/internet/InternetTest.cpp index 7fcbe661..cc633228 100644 --- a/src/modules/internet/InternetTest.cpp +++ b/src/modules/internet/InternetTest.cpp @@ -618,3 +618,17 @@ TEST_F(InternetTest, shouldGenerateIpv4KeepingTheMaskedPart) ASSERT_EQ(sampleAddress[0], generatedAddress[0]); ASSERT_TRUE((generatedAddress[1] & generationMask[1]) == expectedSecondSectorMaskedValue); } + +TEST_F(InternetTest, MacDefaultSeparator) +{ + std::string mac = Internet::mac(); + ASSERT_EQ(mac.size(), 17); + + for (size_t i = 0; i < mac.size(); i += 3) { + ASSERT_TRUE(isxdigit(mac[i])); + } + + for (size_t i = 2; i < mac.size(); i += 3) { + ASSERT_EQ(mac[i], ':'); + } +} diff --git a/src/modules/number/Number.cpp b/src/modules/number/Number.cpp index a5df1d72..50947f29 100644 --- a/src/modules/number/Number.cpp +++ b/src/modules/number/Number.cpp @@ -1,8 +1,36 @@ #include "faker-cxx/Number.h" +#include + namespace faker { std::random_device Number::randomDevice; std::mt19937 Number::pseudoRandomGenerator(Number::randomDevice()); + +std::string Number::hex(std::optional min, std::optional max) +{ + int defaultMin = 0; + int defaultMax = 15; + + if (min.has_value()) { + defaultMin = min.value(); + } + + if (max.has_value()) { + defaultMax = max.value(); + } + + + return convertToHex(integer(defaultMin, defaultMax)); +} + +std::string Number::convertToHex(int number) +{ + std::stringstream sstream; + sstream << std::hex << number; + std::string hexString = sstream.str(); + return hexString; +} + } diff --git a/src/modules/number/NumberTest.cpp b/src/modules/number/NumberTest.cpp index 4b7a2b21..e121225b 100644 --- a/src/modules/number/NumberTest.cpp +++ b/src/modules/number/NumberTest.cpp @@ -1,6 +1,7 @@ #include "faker-cxx/Number.h" #include +#include #include "gtest/gtest.h" @@ -75,3 +76,25 @@ TEST_F(NumberTest, givenRealDistribution_shouldGenerateNumberThatIsInGivenRange) ASSERT_TRUE(actualRandomNumber >= 2.f); ASSERT_TRUE(actualRandomNumber <= 10.f); } + +TEST_F(NumberTest, HexMethodTest) +{ + + std::string result = Number::hex(); + ASSERT_EQ(result.size(), 1); + ASSERT_TRUE(std::isxdigit(result[0])); + + result = Number::hex(100, 255); + ASSERT_EQ(result.size(), 2); + ASSERT_TRUE(std::isxdigit(result[0])); + ASSERT_TRUE(std::isxdigit(result[1])); + + result = Number::hex(10, 15); + ASSERT_EQ(result.size(), 1); + ASSERT_TRUE(std::isxdigit(result[0])); + + result = Number::hex(30, 40); + ASSERT_EQ(result.size(), 2); + ASSERT_TRUE(std::isxdigit(result[0])); + ASSERT_TRUE(std::isxdigit(result[1])); +} diff --git a/src/modules/system/System.cpp b/src/modules/system/System.cpp new file mode 100644 index 00000000..01620703 --- /dev/null +++ b/src/modules/system/System.cpp @@ -0,0 +1,235 @@ +#include "faker-cxx/System.h" + +namespace faker +{ +std::string System::fileName(const FileOptions& options) +{ + std::string baseName = Word::words(); + std::string extensionsStr; + + if (options.extensionCount > 0) + { + std::vector randomExtensions; + if (options.extensionRange.min == options.extensionRange.max) + { + for (int i = 0; i < options.extensionCount; ++i) + { + std::string randomExt = fileExt(); + randomExtensions.push_back(randomExt); + } + extensionsStr = "." + StringHelper::join(randomExtensions, "."); + } + else + { + int numExtensions; + numExtensions = + options.extensionRange.min + rand() % (options.extensionRange.max - options.extensionRange.min + 1); + + for (int i = 0; i < numExtensions; ++i) + { + std::string randomExt = fileExt(); + randomExtensions.push_back(randomExt); + } + + extensionsStr = "." + StringHelper::join(randomExtensions, "."); + } + } + return baseName + extensionsStr; +} + +std::string System::fileExt(const std::optional& mimeType) +{ + if (mimeType.has_value() && !mimeType->empty()) + { + auto it = std::find(mimeTypes.begin(), mimeTypes.end(), *mimeType); + if (it != mimeTypes.end()) + { + const std::string& extension = *it; + size_t pos = extension.find_last_of('/'); + return extension.substr(pos + 1); + } + } + else + { + std::set extensionSet; + + for (const auto& extension : mimeTypes) + { + size_t pos = extension.find_last_of('/'); + extensionSet.insert(extension.substr(pos + 1)); + } + + std::vector extensions(extensionSet.begin(), extensionSet.end()); + return Helper::arrayElement(extensions); + } + return ""; +} + +std::string System::commonFileName(const std::optional& ext) +{ + FileOptions options; + options.extensionCount = 0; + std::string str = fileName(options); + if (ext.has_value() && !ext.value().empty()) + { + return str + "." + ext.value(); + } + else + { + return str + "." + commonFileExt(); + } +} + +std::string System::commonFileExt() +{ + std::optional mimeType = Helper::arrayElement(commonMimeTypes); + return fileExt(mimeType); +} + +std::string System::mimeType() +{ + std::vector mimeTypeKeys; + for (const auto& entry : mimeTypes) + { + mimeTypeKeys.push_back(entry); + } + + return Helper::arrayElement(mimeTypeKeys); +} + +std::string System::commonFileType() +{ + return Helper::arrayElement(commonFileTypes); +} + +std::string System::fileType() +{ + std::set typeSet; + const auto& localMimeTypes = mimeTypes; + + for (const auto& entry : localMimeTypes) + { + const std::string& m = entry; + size_t pos = m.find('/'); + if (pos != std::string::npos) + { + std::string type = m.substr(0, pos); + typeSet.insert(type); + } + } + + std::vector types(typeSet.begin(), typeSet.end()); + return Helper::arrayElement(types); +} + +std::string System::directoryPath() +{ + const std::vector paths = directoryPaths; + return Helper::arrayElement(paths); +} +std::string System::filePath() +{ + return directoryPath() + fileName(); +} + +std::string System::semver() +{ + int major = Number::integer(9); + int minor = Number::integer(9); + int patch = Number::integer(9); + + std::stringstream ss; + ss << major << '.' << minor << '.' << patch; + return ss.str(); +} + +std::string System::networkInterface(const std::optional& options) +{ + const auto defaultInterfaceType = Helper::arrayElement(commonInterfaceTypes); + const std::string defaultInterfaceSchema = Helper::objectKey(commonInterfaceSchemas); + + std::string interfaceType = defaultInterfaceType; + std::string interfaceSchema = defaultInterfaceSchema; + + if (options.has_value()) { + if (options->interfaceType.has_value() && !options->interfaceType.value().empty()) { + interfaceType = options->interfaceType.value(); + } + + if (options->interfaceSchema.has_value() && !options->interfaceSchema.value().empty()) { + interfaceSchema = options->interfaceSchema.value(); + } + } + + std::string suffix; + std::string prefix = ""; + auto digit = []() { return String::numeric(); }; + + if (interfaceSchema == "index") + { + suffix = digit(); + } + else if (interfaceSchema == "slot") + { + suffix = Helper::maybe([&]() { return "f" + digit(); }); + suffix += Helper::maybe([&]() { return "d" + digit(); }); + } + else if (interfaceSchema == "mac") + { + suffix = Internet::mac(""); + } + else if (interfaceSchema == "pci") + { + prefix = Helper::maybe([&]() { return "P" + digit(); }); + suffix = digit() + "s" + digit(); + suffix += Helper::maybe([&]() { return "f" + digit(); }); + suffix += Helper::maybe([&]() { return "d" + digit(); }); + } + + return prefix + interfaceType + commonInterfaceSchemas.at(interfaceSchema) + suffix; +} +std::string System::cron(const CronOptions& options) +{ + bool includeYear = options.includeYear; + bool includeNonStandard = options.includeNonStandard; + std::vector minutes = { std::to_string(Number::integer(59)), "*" }; + std::vector hours = { std::to_string(Number::integer(23)), "*" }; + std::vector days = { std::to_string(Number::integer(1, 31)), "*", "?" }; + std::vector months = { std::to_string(Number::integer(1, 12)), "*" }; + std::vector daysOfWeek = { + std::to_string(Number::integer(6)), + cronDayOfWeek[static_cast(Number::integer(0, static_cast(cronDayOfWeek.size() - 1)))], + "*", + "?" + }; + + std::vector years; + if (includeYear) { + years.push_back(std::to_string(Number::integer(1970, 2099))); + } + else + { + years = { std::to_string(Number::integer(1970, 2099)), "*" }; + } + + auto minute = Helper::arrayElement(minutes); + auto hour = Helper::arrayElement(hours); + auto day = Helper::arrayElement(days); + auto month = Helper::arrayElement(months); + auto dayOfWeek = Helper::arrayElement(daysOfWeek); + auto year = Helper::arrayElement(years); + + std::string standardExpression = minute + " " + hour + " " + day + " " + month + " " + dayOfWeek; + if (includeYear) { + standardExpression += " " + year; + } + + std::vector nonStandardExpressions = { + "@annually", "@daily", "@hourly", "@monthly", "@reboot", "@weekly", "@yearly" + }; + + return (!includeNonStandard || Datatype::boolean(0)) ? + standardExpression : + Helper::arrayElement(nonStandardExpressions); +} +} diff --git a/src/modules/system/SystemTest.cpp b/src/modules/system/SystemTest.cpp new file mode 100644 index 00000000..a863a4de --- /dev/null +++ b/src/modules/system/SystemTest.cpp @@ -0,0 +1,176 @@ +#include "faker-cxx/System.h" + +#include +#include + +#include "gtest/gtest.h" + +using namespace ::testing; +using namespace faker; + +class SystemTest : public Test +{ +}; + +const std::regex validCronPattern( + R"((\*|[0-9]+|\?|\b(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)\b|\b(SUN|MON|TUE|WED|THU|FRI|SAT)\b)( (\*|[0-9]+|\?|\b(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)\b|\b(SUN|MON|TUE|WED|THU|FRI|SAT)\b)){4,5})"); + +bool isValidCronExpression(const std::string& cronExpr) +{ + return std::regex_match(cronExpr, validCronPattern); +} + +TEST_F(SystemTest, FileNameTestWithExtensionCount) +{ + FileOptions options; + options.extensionCount = 3; + + std::string expectedFileName = System::fileName(options); + + EXPECT_FALSE(expectedFileName.empty()); + + FileOptions options2; + options2.extensionRange.min = 1; + options2.extensionRange.max = 3; + + std::string expectedFileName2 = System::fileName(options2); + + EXPECT_FALSE(expectedFileName2.empty()); +} + +TEST_F(SystemTest, FileExtTestWithMimeType) +{ + std::string exampleFileExtension = System::fileExt(); + + EXPECT_FALSE(exampleFileExtension.empty()); + + EXPECT_EQ(System::fileExt("image/png"), "png"); + EXPECT_EQ(System::fileExt("application/pdf"), "pdf"); + EXPECT_EQ(System::fileExt("text/html"), "html"); +} + +TEST_F(SystemTest, CommonFileNameWithEmptyExtensionTest) +{ + + std::string actualFileName = System::commonFileName(); + + std::string actualExtension = actualFileName.substr(actualFileName.find_last_of('.') + 1); + EXPECT_FALSE(actualExtension.empty()); + + std::string fileNameWithParam = System::commonFileName("txt"); + + std::string extensionWithParam = fileNameWithParam.substr(fileNameWithParam.find_last_of('.') + 1); + + EXPECT_EQ(extensionWithParam, "txt"); +} + +TEST_F(SystemTest, MimeTypeTest) +{ + std::string mimeTypeResult = System::mimeType(); + + bool isValidMimeType = std::find(mimeTypes.begin(), mimeTypes.end(), mimeTypeResult) != mimeTypes.end(); + EXPECT_TRUE(isValidMimeType); +} + +TEST_F(SystemTest, CommonFileTypeTest) +{ + std::string commonFileTypeResult = System::commonFileType(); + + bool isValidCommonFileType = + std::find(commonFileTypes.begin(), commonFileTypes.end(), commonFileTypeResult) != commonFileTypes.end(); + EXPECT_TRUE(isValidCommonFileType); +} + +TEST_F(SystemTest, FileTypeTest) +{ + std::set typeSet; + for (const auto& entry : mimeTypes) + { + const std::string& m = entry; + size_t pos = m.find('/'); + if (pos != std::string::npos) + { + std::string type = m.substr(0, pos); + typeSet.insert(type); + } + } + std::vector expectedTypes(typeSet.begin(), typeSet.end()); + + std::string fileTypeResult = System::fileType(); + + bool isValidFileType = std::find(expectedTypes.begin(), expectedTypes.end(), fileTypeResult) != expectedTypes.end(); + EXPECT_TRUE(isValidFileType); +} + +TEST_F(SystemTest, FilePathTest) +{ + std::string filePath = System::filePath(); + + EXPECT_FALSE(filePath.empty()); +} + +TEST_F(SystemTest, SemverTest) +{ + std::string semverResult = System::semver(); + + EXPECT_TRUE(std::regex_match(semverResult, std::regex("\\d+\\.\\d+\\.\\d+"))); +} + +TEST_F(SystemTest, NetworkInterfaceMethodTest) +{ + + std::string result1 = System::networkInterface(std::nullopt); + ASSERT_TRUE(!result1.empty()); + + NetworkInterfaceOptions options2; + options2.interfaceType = "wl"; + std::string result2 = System::networkInterface(options2); + ASSERT_TRUE(!result2.empty()); + + NetworkInterfaceOptions options3; + options3.interfaceSchema = "mac"; + std::string result3 = System::networkInterface(options3); + ASSERT_EQ(result3.size(), 15); + + NetworkInterfaceOptions options4; + options4.interfaceType = "en"; + options4.interfaceSchema = "pci"; + std::string result4 = System::networkInterface(options4); + ASSERT_TRUE(!result4.empty()); +} + +TEST_F(SystemTest, ValidCronExpression) +{ + std::string cronExpr = System::cron(); + EXPECT_TRUE(isValidCronExpression(cronExpr)); +} + +TEST_F(SystemTest, IncludeYearOption) +{ + CronOptions options; + options.includeYear = true; + std::string cronExpr = System::cron(options); + EXPECT_TRUE(isValidCronExpression(cronExpr)); + + int yearValue = -1; + std::smatch match; + if (std::regex_search(cronExpr, match, std::regex(R"(\b(19[7-9][0-9]|20[0-9]{2})\b)"))) + { + yearValue = std::stoi(match.str()); + } + EXPECT_GE(yearValue, 1970); + EXPECT_LE(yearValue, 2099); +} + +TEST_F(SystemTest, IncludeNonStandardOption) +{ + CronOptions options; + options.includeNonStandard = true; + std::string cronExpr = System::cron(options); + + std::vector nonStandardExpressions = {"@annually", "@daily", "@hourly", "@monthly", + "@reboot", "@weekly", "@yearly"}; + bool isNonStandard = std::find(nonStandardExpressions.begin(), nonStandardExpressions.end(), cronExpr) != + nonStandardExpressions.end(); + EXPECT_TRUE(isNonStandard || isValidCronExpression(cronExpr)); +}