Skip to content

Commit

Permalink
feature: Implemented string guarantee for String::alphanumeric() (#351
Browse files Browse the repository at this point in the history
)

* feature: Implemented string guarantee for `String::alphanumeric()`

* tests: Added tests for string guarantee in `String::alphanumeric()`
  • Loading branch information
braw-lee authored Nov 28, 2023
1 parent f0f5e54 commit b6a5efc
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 3 deletions.
18 changes: 18 additions & 0 deletions include/faker-cxx/String.h
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,24 @@ class String
static std::string alphanumeric(unsigned length = 1, StringCasing casing = StringCasing::Mixed,
const std::string& excludeCharacters = "");

/**
* @brief Generates a string consisting of alpha characters and digits.
*
* @param guarantee A map specifying char count constraints if any
* @param length The number of characters to generate. Defaults to `1`.
* @param casing The casing of the characters. Defaults to `StringCasing::Mixed`.
*
* @returns Alphanumeric string.
*
* @code
* String::alphanumeric({}) // "4"
* String::alphanumeric({{'A', {3,6}},{'1', {1,1}}, 5, StringCasing::Upper) // "1EAAA"
* String::alphanumeric({{'a',{0,2}},{'2',{0,3}},{'z',{3,5}}}, 10, StringCasing::Lower) // "z1naazrqz0"
* @endcode
*/
static std::string alphanumeric(GuaranteeMap&& guarantee, unsigned length = 1,
StringCasing casing = StringCasing::Mixed);

/**
* @brief Generates a given length string of digits.
*
Expand Down
13 changes: 13 additions & 0 deletions src/modules/string/String.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,19 @@ std::string String::alphanumeric(unsigned int length, StringCasing casing, const
return alphanumeric;
}

std::string String::alphanumeric(GuaranteeMap&& guarantee, unsigned length, StringCasing casing)
{
auto targetCharacters = digitSet;
auto charSet = stringCasingToAlphaCharSetMapping.at(casing);
targetCharacters.merge(charSet);
// throw if guarantee is invalid
if (!isValidGuarantee(guarantee, targetCharacters, length))
{
throw std::invalid_argument{"Invalid guarantee."};
}
return generateStringWithGuarantee(guarantee, targetCharacters, length);
}

std::string String::numeric(unsigned int length, bool allowLeadingZeros)
{
std::string alphanumeric;
Expand Down
162 changes: 159 additions & 3 deletions src/modules/string/StringTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ TEST_F(StringTest, shouldGenerateMixedAlphaWithGuarantee)
// exactly 5 'a'
// atleast 5 'A' - 3 'B' - 3 'z'
// atmost 20 'A' - 20 'B' - 6 'z'
const GuaranteeMap&& guarantee{{'A', {5, 20}}, {'B', {3, 20}}, {'a', {5, 5}}, {'z', {3, 6}}};
const GuaranteeMap guarantee{{'A', {5, 20}}, {'B', {3, 20}}, {'a', {5, 5}}, {'z', {3, 6}}};
// it is a random function so lets test for 20 random generations
for (int i = 0; i < runCount; ++i)
{
Expand Down Expand Up @@ -260,7 +260,7 @@ TEST_F(StringTest, shouldGenerateLowerAlphaWithGuarantee)
// exactly 5 'a'
// atleast 5 'k' - 3 'o' - 3 'z'
// atmost 20 'k' - 20 'o' - 6 'z'
const GuaranteeMap&& guarantee{{'k', {5, 20}}, {'o', {3, 20}}, {'a', {5, 5}}, {'z', {3, 6}}};
const GuaranteeMap guarantee{{'k', {5, 20}}, {'o', {3, 20}}, {'a', {5, 5}}, {'z', {3, 6}}};
// it is a random function so lets test for 20 random generations
for (int i = 0; i < runCount; ++i)
{
Expand Down Expand Up @@ -293,7 +293,7 @@ TEST_F(StringTest, shouldGenerateUpperAlphaWithGuarantee)
// exactly 5 'A'
// atleast 5 'K' - 3 'O' - 3 'Z'
// atmost 20 'K' - 20 'O' - 6 'Z'
const GuaranteeMap&& guarantee{{'K', {5, 20}}, {'O', {3, 20}}, {'A', {5, 5}}, {'Z', {3, 6}}};
const GuaranteeMap guarantee{{'K', {5, 20}}, {'O', {3, 20}}, {'A', {5, 5}}, {'Z', {3, 6}}};
// it is a random function so lets test for 20 random generations
for (int i = 0; i < runCount; ++i)
{
Expand Down Expand Up @@ -428,6 +428,162 @@ TEST_F(StringTest, shouldGenerateLowerAlphanumeric)
}));
}

TEST_F(StringTest, shouldGenerateMixedAlphanumericWithGuarantee)
{
const auto alphanumericLength = 20;
// exactly 1 'a'
// atleast 5 'A' - 3 'B' - 3 'z'
// atmost 20 'A' - 20 'B' - 6 'z'
const GuaranteeMap guarantee{{'1', {5, 20}}, {'B', {3, 20}}, {'a', {5, 5}}, {'z', {3, 6}}};
// it is a random function so lets test for 20 random generations
for (int i = 0; i < runCount; ++i)
{
auto copyGuarantee = guarantee;
const auto alphanumeric = String::alphanumeric(std::move(copyGuarantee), alphanumericLength);

ASSERT_EQ(alphanumeric.size(), alphanumericLength);
ASSERT_TRUE(std::ranges::all_of(
alphanumeric,
[](char alphanumericCharacter)
{
return std::ranges::any_of(mixedAlphanumericCharacters, [alphanumericCharacter](char mixedCharacter)
{ return mixedCharacter == alphanumericCharacter; });
}));
auto count_1 = std::ranges::count(alphanumeric, '1');
auto count_B = std::ranges::count(alphanumeric, 'B');
auto count_a = std::ranges::count(alphanumeric, 'a');
auto count_z = std::ranges::count(alphanumeric, 'z');

ASSERT_TRUE(count_1 >= 5 && count_1 <= 20);
ASSERT_TRUE(count_B >= 3 && count_B <= 20);
ASSERT_TRUE(count_a == 5);
ASSERT_TRUE(count_z >= 3 && count_z <= 6);
}
}

TEST_F(StringTest, shouldGenerateLowerAlphanumericWithGuarantee)
{
const auto alphanumericLength = 20;
// exactly 5 'a'
// atleast 5 'k' - 3 'o' - 3 '0'
// atmost 20 'k' - 20 'o' - 6 '0'
const GuaranteeMap guarantee{{'k', {5, 20}}, {'o', {3, 20}}, {'a', {5, 5}}, {'0', {3, 6}}};
// it is a random function so lets test for 20 random generations
for (int i = 0; i < runCount; ++i)
{
auto copyGuarantee = guarantee;
const auto alphanumeric =
String::alphanumeric(std::move(copyGuarantee), alphanumericLength, StringCasing::Lower);

ASSERT_EQ(alphanumeric.size(), alphanumericLength);
ASSERT_TRUE(std::ranges::all_of(
alphanumeric,
[](char alphanumericCharacter)
{
return std::ranges::any_of(lowerAlphanumericCharacters, [alphanumericCharacter](char lowerCharacter)
{ return lowerCharacter == alphanumericCharacter; });
}));
auto count_k = std::ranges::count(alphanumeric, 'k');
auto count_o = std::ranges::count(alphanumeric, 'o');
auto count_a = std::ranges::count(alphanumeric, 'a');
auto count_0 = std::ranges::count(alphanumeric, '0');

ASSERT_TRUE(count_k >= 5 && count_k <= 20);
ASSERT_TRUE(count_o >= 3 && count_o <= 20);
ASSERT_TRUE(count_a == 5);
ASSERT_TRUE(count_0 >= 3 && count_0 <= 6);
}
}

TEST_F(StringTest, shouldGenerateUpperAlphanumericWithGuarantee)
{
const auto alphanumericLength = 20;
// exactly 5 'A'
// atleast 5 '7' - 3 'O' - 3 'Z'
// atmost 20 '7' - 20 'O' - 6 'Z'
const GuaranteeMap guarantee{{'7', {5, 20}}, {'O', {3, 20}}, {'A', {5, 5}}, {'Z', {3, 6}}};
// it is a random function so lets test for 20 random generations
for (int i = 0; i < runCount; ++i)
{
auto copyGuarantee = guarantee;
const auto alphanumeric =
String::alphanumeric(std::move(copyGuarantee), alphanumericLength, StringCasing::Upper);

ASSERT_EQ(alphanumeric.size(), alphanumericLength);
ASSERT_TRUE(std::ranges::all_of(
alphanumeric,
[](char alphanumericCharacter)
{
return std::ranges::any_of(upperAlphanumericCharacters, [alphanumericCharacter](char lowerCharacter)
{ return lowerCharacter == alphanumericCharacter; });
}));
auto count_7 = std::ranges::count(alphanumeric, '7');
auto count_O = std::ranges::count(alphanumeric, 'O');
auto count_A = std::ranges::count(alphanumeric, 'A');
auto count_Z = std::ranges::count(alphanumeric, 'Z');

ASSERT_TRUE(count_7 >= 5 && count_7 <= 20);
ASSERT_TRUE(count_O >= 3 && count_O <= 20);
ASSERT_TRUE(count_A == 5);
ASSERT_TRUE(count_Z >= 3 && count_Z <= 6);
}
}

TEST_F(StringTest, invalidGuaranteeForAlphanumeric1)
{
const auto alphanumericLength = 20;
// exactly 3 '8'
// atleast 8 'A' - 10 'b' 1 'Y' // invalid // string size will be atleast 22 which is invalid
// atmost 10 'A','Y' - 15 'b'
GuaranteeMap guarantee = {{'A', {8, 10}}, {'b', {10, 15}}, {'Y', {1, 10}}, {'8', {3, 3}}};
ASSERT_THROW(String::alphanumeric(std::move(guarantee), alphanumericLength), std::invalid_argument);
}

TEST_F(StringTest, invalidGuaranteeForAlphanumeric2)
{
const auto alphanumericLength = 40;
// atmost 1
// 'A','B','C',D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R',
// 'S','T','U','V','W','X','Y','Z','0','1','2','3','4','5','6','7','8','9'
// invalid // string size won't exceed 36 which is invalid
GuaranteeMap guarantee = {
{'A', {0, 1}}, {'B', {0, 1}}, {'C', {0, 1}}, {'D', {0, 1}}, {'E', {0, 1}}, {'F', {0, 1}},
{'G', {0, 1}}, {'H', {0, 1}}, {'I', {0, 1}}, {'J', {0, 1}}, {'K', {0, 1}}, {'L', {0, 1}},
{'M', {0, 1}}, {'N', {0, 1}}, {'O', {0, 1}}, {'P', {0, 1}}, {'Q', {0, 1}}, {'R', {0, 1}},
{'S', {0, 1}}, {'T', {0, 1}}, {'U', {0, 1}}, {'V', {0, 1}}, {'W', {0, 1}}, {'X', {0, 1}},
{'Y', {0, 1}}, {'Z', {0, 1}}, {'0', {0, 1}}, {'1', {0, 1}}, {'2', {0, 1}}, {'3', {0, 1}},
{'4', {0, 1}}, {'5', {0, 1}}, {'6', {0, 1}}, {'7', {0, 1}}, {'8', {0, 1}}, {'9', {0, 1}},
};
ASSERT_THROW(String::alphanumeric(std::move(guarantee), alphanumericLength, StringCasing::Upper),
std::invalid_argument);
}

TEST_F(StringTest, invalidGuaranteeForAlphanumeric3)
{
const auto alphanumericLength = 20;
// atleast 4 '#' // invalid // alphanumeric can't have symbols
GuaranteeMap guarantee = {{'a', {4, 10}}, {'B', {4, 10}}, {'5', {4, 6}}, {'#', {4}}};
ASSERT_THROW(String::alphanumeric(std::move(guarantee), alphanumericLength), std::invalid_argument);
}

TEST_F(StringTest, invalidGuaranteeForAlphanumeric4)
{
const auto alphanumericLength = 20;
// atleast 4 'a' // invalid // Can't have lower case characters when string casing is set to StringCasing::Upper
GuaranteeMap guarantee = {{'a', {4, 10}}, {'B', {4, 10}}, {'2', {1}}};
ASSERT_THROW(String::alphanumeric(std::move(guarantee), alphanumericLength, StringCasing::Upper),
std::invalid_argument);
}

TEST_F(StringTest, invalidGuaranteeForAlphanumeric5)
{
const auto alphanumericLength = 20;
// atleast 4 'B' // invalid // Can't have upper case characters when string casing is set to StringCasing::Lower
GuaranteeMap guarantee = {{'a', {4, 10}}, {'B', {4, 10}}, {'8', {8, 10}}};
ASSERT_THROW(String::alphanumeric(std::move(guarantee), alphanumericLength, StringCasing::Lower),
std::invalid_argument);
}

TEST_F(StringTest, shouldGenerateNumeric)
{
const auto numeric = String::numeric();
Expand Down

0 comments on commit b6a5efc

Please sign in to comment.