Add unit tests for Base64Url encode and decode methods in Core (#6804)

* Initial plan

* Add comprehensive unit tests for Base64Url encode and decode methods

Co-authored-by: RickWinter <4430337+RickWinter@users.noreply.github.com>

* Fix clang-format for base64_test.cpp

Co-authored-by: RickWinter <4430337+RickWinter@users.noreply.github.com>

* Fix misleading comments in Base64Url tests

Co-authored-by: RickWinter <4430337+RickWinter@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: RickWinter <4430337+RickWinter@users.noreply.github.com>
This commit is contained in:
Copilot 2025-10-23 11:38:03 -07:00 committed by GitHub
parent c583f4af14
commit 32b67d1798
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -186,3 +186,225 @@ TEST(Base64, InvalidDecode)
// cspell::enable
}
// Base64Url Tests
TEST(Base64Url, BasicEncode)
{
// Test empty input
std::vector<uint8_t> emptyData;
std::string result = _internal::Base64Url::Base64UrlEncode(emptyData);
EXPECT_EQ(result, "");
// Test single byte (padding should be removed)
std::vector<uint8_t> oneByte = {0x01};
result = _internal::Base64Url::Base64UrlEncode(oneByte);
EXPECT_EQ(result, "AQ"); // Base64 would be "AQ==", Base64Url removes padding
// Test two bytes (padding should be removed)
std::vector<uint8_t> twoBytes = {0x01, 0x02};
result = _internal::Base64Url::Base64UrlEncode(twoBytes);
EXPECT_EQ(result, "AQI"); // Base64 would be "AQI=", Base64Url removes padding
// Test three bytes (no padding needed)
std::vector<uint8_t> threeBytes = {0x01, 0x02, 0x03};
result = _internal::Base64Url::Base64UrlEncode(threeBytes);
EXPECT_EQ(result, "AQID"); // cspell:disable-line
// Test data with + and / in Base64 encoding (should be replaced with - and _)
// Base64 for these bytes would contain '+' and '/', which should be replaced in Base64Url
std::vector<uint8_t> specialChars = {0xFB, 0xEF};
result = _internal::Base64Url::Base64UrlEncode(specialChars);
// Verify it doesn't contain + or /
EXPECT_EQ(result.find('+'), std::string::npos);
EXPECT_EQ(result.find('/'), std::string::npos);
// Test data that generates + in Base64
std::vector<uint8_t> dataWithPlus = {0xFB}; // This should generate '+' in standard Base64
result = _internal::Base64Url::Base64UrlEncode(dataWithPlus);
EXPECT_EQ(result.find('+'), std::string::npos);
EXPECT_NE(result.find('-'), std::string::npos); // Should have '-' instead
// Test data that generates / in Base64
std::vector<uint8_t> dataWithSlash = {0xFF}; // This should generate '/' in standard Base64
result = _internal::Base64Url::Base64UrlEncode(dataWithSlash);
EXPECT_EQ(result.find('/'), std::string::npos);
EXPECT_NE(result.find('_'), std::string::npos); // Should have '_' instead
}
TEST(Base64Url, BasicDecode)
{
// Test empty input
std::vector<uint8_t> result = _internal::Base64Url::Base64UrlDecode("");
EXPECT_TRUE(result.empty());
// Test single byte (no padding in input)
result = _internal::Base64Url::Base64UrlDecode("AQ");
EXPECT_EQ(result.size(), 1u);
EXPECT_EQ(result[0], 0x01);
// Test two bytes (no padding in input)
result = _internal::Base64Url::Base64UrlDecode("AQI");
EXPECT_EQ(result.size(), 2u);
EXPECT_EQ(result[0], 0x01);
EXPECT_EQ(result[1], 0x02);
// Test three bytes
result = _internal::Base64Url::Base64UrlDecode("AQID"); // cspell:disable-line
EXPECT_EQ(result.size(), 3u);
EXPECT_EQ(result[0], 0x01);
EXPECT_EQ(result[1], 0x02);
EXPECT_EQ(result[2], 0x03);
// Test with URL-safe characters (- and _)
std::vector<uint8_t> encoded = {0xFB, 0xEF};
std::string base64UrlEncoded = _internal::Base64Url::Base64UrlEncode(encoded);
result = _internal::Base64Url::Base64UrlDecode(base64UrlEncoded);
EXPECT_EQ(result, encoded);
}
TEST(Base64Url, RoundtripEncodeDecode)
{
// Test various lengths to verify padding handling
for (size_t len : {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100, 1000})
{
std::vector<uint8_t> data;
data.resize(len);
RandomBuffer(data.data(), data.size());
std::string encoded = _internal::Base64Url::Base64UrlEncode(data);
std::vector<uint8_t> decoded = _internal::Base64Url::Base64UrlDecode(encoded);
EXPECT_EQ(decoded, data) << "Roundtrip failed for length " << len;
// Verify no padding characters in encoded output
EXPECT_EQ(encoded.find('='), std::string::npos)
<< "Base64Url encoded string should not contain padding";
// Verify no standard Base64 special chars (+ and /)
EXPECT_EQ(encoded.find('+'), std::string::npos)
<< "Base64Url encoded string should not contain '+'";
EXPECT_EQ(encoded.find('/'), std::string::npos)
<< "Base64Url encoded string should not contain '/'";
}
}
TEST(Base64Url, SpecialCharacterReplacement)
{
// Create data that will produce '+' and '/' in standard Base64
// 0x3E in 6-bit encoding = 62 = '+' in Base64, should be '-' in Base64Url
// 0x3F in 6-bit encoding = 63 = '/' in Base64, should be '_' in Base64Url
// Pattern that generates both + and /
// We need bytes that when encoded produce these characters
std::vector<uint8_t> data;
for (int i = 0; i < 256; i++)
{
data.push_back(static_cast<uint8_t>(i));
}
std::string encoded = _internal::Base64Url::Base64UrlEncode(data);
// Verify the encoding uses URL-safe characters
EXPECT_EQ(encoded.find('+'), std::string::npos) << "Should not contain '+'";
EXPECT_EQ(encoded.find('/'), std::string::npos) << "Should not contain '/'";
// Verify roundtrip works
std::vector<uint8_t> decoded = _internal::Base64Url::Base64UrlDecode(encoded);
EXPECT_EQ(decoded, data);
}
TEST(Base64Url, PaddingHandling)
{
// Test that Base64Url decode correctly adds padding
// Length % 4 == 2 should add "=="
std::vector<uint8_t> data1 = {0x01};
std::string encoded1 = _internal::Base64Url::Base64UrlEncode(data1);
EXPECT_EQ(encoded1.length() % 4, 2u);
EXPECT_EQ(encoded1.find('='), std::string::npos);
std::vector<uint8_t> decoded1 = _internal::Base64Url::Base64UrlDecode(encoded1);
EXPECT_EQ(decoded1, data1);
// Length % 4 == 3 should add "="
std::vector<uint8_t> data2 = {0x01, 0x02};
std::string encoded2 = _internal::Base64Url::Base64UrlEncode(data2);
EXPECT_EQ(encoded2.length() % 4, 3u);
EXPECT_EQ(encoded2.find('='), std::string::npos);
std::vector<uint8_t> decoded2 = _internal::Base64Url::Base64UrlDecode(encoded2);
EXPECT_EQ(decoded2, data2);
// Length % 4 == 0 should not add padding
std::vector<uint8_t> data3 = {0x01, 0x02, 0x03};
std::string encoded3 = _internal::Base64Url::Base64UrlEncode(data3);
EXPECT_EQ(encoded3.length() % 4, 0u);
std::vector<uint8_t> decoded3 = _internal::Base64Url::Base64UrlDecode(encoded3);
EXPECT_EQ(decoded3, data3);
}
TEST(Base64Url, InvalidDecodeInput)
{
// Test length % 4 == 1 (invalid)
EXPECT_THROW(
_internal::Base64Url::Base64UrlDecode("A"), // cspell:disable-line
std::invalid_argument);
EXPECT_THROW(
_internal::Base64Url::Base64UrlDecode("AAAAA"), // cspell:disable-line
std::invalid_argument);
EXPECT_THROW(
_internal::Base64Url::Base64UrlDecode("AAAAAAAAA"), // cspell:disable-line
std::invalid_argument);
// Test invalid characters (that aren't valid in Base64)
EXPECT_THROW(_internal::Base64Url::Base64UrlDecode("@@@@"), std::runtime_error);
EXPECT_THROW(_internal::Base64Url::Base64UrlDecode("A@@@"), std::runtime_error);
EXPECT_THROW(_internal::Base64Url::Base64UrlDecode("####"), std::runtime_error);
}
TEST(Base64Url, ComparisonWithStandardBase64)
{
// Create test data
std::vector<uint8_t> data = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
// Encode with standard Base64
std::string base64 = Convert::Base64Encode(data);
// Encode with Base64Url
std::string base64Url = _internal::Base64Url::Base64UrlEncode(data);
// Base64Url should not have padding
EXPECT_NE(base64.find('='), std::string::npos); // Standard Base64 should have padding
EXPECT_EQ(base64Url.find('='), std::string::npos); // Base64Url should not
// Both should decode to the same data
EXPECT_EQ(Convert::Base64Decode(base64), data);
EXPECT_EQ(_internal::Base64Url::Base64UrlDecode(base64Url), data);
}
TEST(Base64Url, KnownVectors)
{
// Test with known Base64 -> Base64Url conversions
// Standard Base64: "hello" -> "aGVsbG8="
// Base64Url should be: "aGVsbG8" (no padding)
std::vector<uint8_t> hello = {'h', 'e', 'l', 'l', 'o'};
std::string encoded = _internal::Base64Url::Base64UrlEncode(hello);
EXPECT_EQ(encoded, "aGVsbG8"); // cspell:disable-line
// Decode and verify
std::vector<uint8_t> decoded
= _internal::Base64Url::Base64UrlDecode("aGVsbG8"); // cspell:disable-line
EXPECT_EQ(decoded, hello);
// Test case with special character replacement needs
// Using bytes that create both + and / in standard Base64
// Standard Base64 for {0xFB, 0xFF, 0xFE} is "+//+", which includes both + and /
std::vector<uint8_t> specialData = {0xFB, 0xFF, 0xFE};
std::string specialEncoded = _internal::Base64Url::Base64UrlEncode(specialData);
// Verify URL-safe
EXPECT_EQ(specialEncoded.find('+'), std::string::npos);
EXPECT_EQ(specialEncoded.find('/'), std::string::npos);
// Verify roundtrip
std::vector<uint8_t> specialDecoded = _internal::Base64Url::Base64UrlDecode(specialEncoded);
EXPECT_EQ(specialDecoded, specialData);
}