diff --git a/test/jose/jose_jwk_private_test.cc b/test/jose/jose_jwk_private_test.cc index a27544d52f..0e5c941296 100644 --- a/test/jose/jose_jwk_private_test.cc +++ b/test/jose/jose_jwk_private_test.cc @@ -253,3 +253,59 @@ TEST(jwk_private_from_octets) { EXPECT_FALSE(key.algorithm().has_value()); EXPECT_FALSE(key.public_jwk().has_value()); } + +TEST(jwk_private_from_json_rejects_unknown_key_type) { + EXPECT_FALSE(sourcemeta::core::JWKPrivate::from( + sourcemeta::core::parse_json(R"({"kty":"XYZ","d":"abc"})")) + .has_value()); +} + +TEST(jwk_private_from_json_rejects_missing_key_type) { + EXPECT_FALSE(sourcemeta::core::JWKPrivate::from( + sourcemeta::core::parse_json(R"({"crv":"P-256","d":"abc"})")) + .has_value()); +} + +TEST(jwk_private_from_json_rejects_non_string_key_type) { + EXPECT_FALSE(sourcemeta::core::JWKPrivate::from( + sourcemeta::core::parse_json(R"({"kty":42})")) + .has_value()); +} + +TEST(jwk_private_from_json_rejects_rsa_multi_prime) { + EXPECT_FALSE(sourcemeta::core::JWKPrivate::from( + sourcemeta::core::parse_json( + R"({"kty":"RSA","n":"n","e":"AQAB","d":"d","p":"p",)" + R"("q":"q","dp":"dp","dq":"dq","qi":"qi","oth":[]})")) + .has_value()); +} + +TEST(jwk_private_from_json_rejects_rsa_missing_component) { + EXPECT_FALSE(sourcemeta::core::JWKPrivate::from( + sourcemeta::core::parse_json( + R"({"kty":"RSA","n":"n","e":"AQAB","d":"d","p":"p",)" + R"("q":"q","dp":"dp","dq":"dq"})")) + .has_value()); +} + +TEST(jwk_private_from_json_rejects_rsa_with_invalid_base64url) { + EXPECT_FALSE(sourcemeta::core::JWKPrivate::from( + sourcemeta::core::parse_json( + R"({"kty":"RSA","n":"!!!","e":"AQAB","d":"d","p":"p",)" + R"("q":"q","dp":"dp","dq":"dq","qi":"qi"})")) + .has_value()); +} + +TEST(jwk_private_from_json_rejects_ec_unknown_curve) { + EXPECT_FALSE(sourcemeta::core::JWKPrivate::from( + sourcemeta::core::parse_json( + R"({"kty":"EC","crv":"P-999","x":"x","y":"y","d":"d"})")) + .has_value()); +} + +TEST(jwk_private_from_json_rejects_okp_unknown_curve) { + EXPECT_FALSE(sourcemeta::core::JWKPrivate::from( + sourcemeta::core::parse_json( + R"({"kty":"OKP","crv":"X25519","x":"x","d":"d"})")) + .has_value()); +} diff --git a/test/json/json_object_test.cc b/test/json/json_object_test.cc index f11a32bf2c..e7f018942e 100644 --- a/test/json/json_object_test.cc +++ b/test/json/json_object_test.cc @@ -1677,3 +1677,116 @@ TEST(unique_keys_true_long_keys) { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2": 2 })")}; EXPECT_TRUE(document.unique_keys()); } + +TEST(long_key_collision_const_at) { + // A perfect hash covers keys up to thirty one bytes, so two keys sharing + // their first thirty one bytes, length, and end bytes collide and force the + // linear scan path + const std::string prefix(31, 'x'); + const std::string first_key{prefix + "ay"}; + const std::string second_key{prefix + "by"}; + sourcemeta::core::JSON document = sourcemeta::core::JSON::make_object(); + document.assign(first_key, sourcemeta::core::JSON{1}); + document.assign(second_key, sourcemeta::core::JSON{2}); + const auto &constant{document}; + EXPECT_EQ(constant.at(first_key).to_integer(), 1); + EXPECT_EQ(constant.at(second_key).to_integer(), 2); +} + +TEST(long_key_collision_mutable_at) { + // A perfect hash covers keys up to thirty one bytes, so two keys sharing + // their first thirty one bytes, length, and end bytes collide and force the + // linear scan path + const std::string prefix(31, 'x'); + const std::string first_key{prefix + "ay"}; + const std::string second_key{prefix + "by"}; + sourcemeta::core::JSON document = sourcemeta::core::JSON::make_object(); + document.assign(first_key, sourcemeta::core::JSON{1}); + document.assign(second_key, sourcemeta::core::JSON{2}); + document.at(second_key) = sourcemeta::core::JSON{9}; + EXPECT_EQ(document.at(first_key).to_integer(), 1); + EXPECT_EQ(document.at(second_key).to_integer(), 9); +} + +TEST(long_key_collision_try_at) { + // A perfect hash covers keys up to thirty one bytes, so two keys sharing + // their first thirty one bytes, length, and end bytes collide and force the + // linear scan path + const std::string prefix(31, 'x'); + const std::string first_key{prefix + "ay"}; + const std::string second_key{prefix + "by"}; + sourcemeta::core::JSON document = sourcemeta::core::JSON::make_object(); + document.assign(first_key, sourcemeta::core::JSON{1}); + document.assign(second_key, sourcemeta::core::JSON{2}); + EXPECT_TRUE(document.try_at(first_key) != nullptr); + EXPECT_EQ(document.try_at(first_key)->to_integer(), 1); + EXPECT_TRUE(document.try_at(second_key) != nullptr); + EXPECT_EQ(document.try_at(second_key)->to_integer(), 2); + const std::string absent{std::string(31, 'x') + "cy"}; + EXPECT_EQ(document.try_at(absent), nullptr); +} + +TEST(long_key_collision_defines) { + // A perfect hash covers keys up to thirty one bytes, so two keys sharing + // their first thirty one bytes, length, and end bytes collide and force the + // linear scan path + const std::string prefix(31, 'x'); + const std::string first_key{prefix + "ay"}; + const std::string second_key{prefix + "by"}; + sourcemeta::core::JSON document = sourcemeta::core::JSON::make_object(); + document.assign(first_key, sourcemeta::core::JSON{1}); + EXPECT_TRUE(document.defines(first_key)); + EXPECT_FALSE(document.defines(second_key)); +} + +TEST(long_key_collision_erase) { + // A perfect hash covers keys up to thirty one bytes, so two keys sharing + // their first thirty one bytes, length, and end bytes collide and force the + // linear scan path + const std::string prefix(31, 'x'); + const std::string first_key{prefix + "ay"}; + const std::string second_key{prefix + "by"}; + sourcemeta::core::JSON document = sourcemeta::core::JSON::make_object(); + document.assign(first_key, sourcemeta::core::JSON{1}); + document.assign(second_key, sourcemeta::core::JSON{2}); + document.erase(first_key); + EXPECT_EQ(document.size(), 1); + EXPECT_FALSE(document.defines(first_key)); + EXPECT_TRUE(document.defines(second_key)); + EXPECT_EQ(document.at(second_key).to_integer(), 2); +} + +TEST(long_key_collision_find) { + // A perfect hash covers keys up to thirty one bytes, so two keys sharing + // their first thirty one bytes, length, and end bytes collide and force the + // linear scan path + const std::string prefix(31, 'x'); + const std::string first_key{prefix + "ay"}; + const std::string second_key{prefix + "by"}; + sourcemeta::core::JSON document = sourcemeta::core::JSON::make_object(); + document.assign(first_key, sourcemeta::core::JSON{1}); + document.assign(second_key, sourcemeta::core::JSON{2}); + const auto &object{document.as_object()}; + const auto first_iterator{object.find(first_key)}; + EXPECT_TRUE(first_iterator != object.cend()); + EXPECT_EQ(first_iterator->second.to_integer(), 1); + const auto second_iterator{object.find(second_key)}; + EXPECT_TRUE(second_iterator != object.cend()); + EXPECT_EQ(second_iterator->second.to_integer(), 2); +} + +TEST(long_key_collision_emplace_updates_existing) { + // A perfect hash covers keys up to thirty one bytes, so two keys sharing + // their first thirty one bytes, length, and end bytes collide and force the + // linear scan path + const std::string prefix(31, 'x'); + const std::string first_key{prefix + "ay"}; + const std::string second_key{prefix + "by"}; + sourcemeta::core::JSON document = sourcemeta::core::JSON::make_object(); + document.assign(first_key, sourcemeta::core::JSON{1}); + document.assign(second_key, sourcemeta::core::JSON{2}); + document.assign(second_key, sourcemeta::core::JSON{7}); + EXPECT_EQ(document.size(), 2); + EXPECT_EQ(document.at(first_key).to_integer(), 1); + EXPECT_EQ(document.at(second_key).to_integer(), 7); +} diff --git a/test/jsonld/jsonld_expand_test.cc b/test/jsonld/jsonld_expand_test.cc index fb80d02456..763b67cba5 100644 --- a/test/jsonld/jsonld_expand_test.cc +++ b/test/jsonld/jsonld_expand_test.cc @@ -517,3 +517,48 @@ TEST(compact_iri_term_with_unresolvable_prefix) { ])JSON")}; EXPECT_EQ(result, expected); } + +TEST(type_redefinition_protected_with_matching_definition) { + const auto input = sourcemeta::core::parse_json(R"({ + "@context": [ + { "@type": { "@container": "@set", "@protected": true } }, + { "@type": { "@container": "@set" } } + ], + "@type": "http://example.com/T" + })"); + + const auto result{sourcemeta::core::jsonld_expand(input)}; + const auto expected{sourcemeta::core::parse_json(R"([ + { "@type": [ "http://example.com/T" ] } + ])")}; + EXPECT_EQ(result, expected); +} + +TEST(simple_term_with_keyword_form_is_dropped) { + const auto input = sourcemeta::core::parse_json(R"({ + "@context": { "@vocab": "http://example.com/", "ignored": "@nest" }, + "ignored": { "http://example.com/foo": "bar" } + })"); + + const auto result{sourcemeta::core::jsonld_expand(input)}; + const auto expected{sourcemeta::core::parse_json(R"([ + { "http://example.com/foo": [ { "@value": "bar" } ] } + ])")}; + EXPECT_EQ(result, expected); +} + +TEST(self_referential_compact_term_via_prefix) { + const auto input = sourcemeta::core::parse_json(R"({ + "@context": { + "ex": "http://example.com/", + "ex:name": "ex:name" + }, + "ex:name": "value" + })"); + + const auto result{sourcemeta::core::jsonld_expand(input)}; + const auto expected{sourcemeta::core::parse_json(R"([ + { "http://example.com/name": [ { "@value": "value" } ] } + ])")}; + EXPECT_EQ(result, expected); +} diff --git a/test/numeric/numeric_decimal_test.cc b/test/numeric/numeric_decimal_test.cc index 2f439f3715..b3e6ea3d24 100644 --- a/test/numeric/numeric_decimal_test.cc +++ b/test/numeric/numeric_decimal_test.cc @@ -4073,3 +4073,225 @@ TEST(exact_from_negative_infinity) { EXPECT_TRUE(value.is_infinite()); EXPECT_TRUE(value.is_signed()); } + +TEST(signalling_nan_payload_string) { + EXPECT_EQ(sourcemeta::core::Decimal::snan(123).to_string(), "sNaN123"); +} + +TEST(negative_signalling_nan_round_trip) { + const sourcemeta::core::Decimal value{"-sNaN123"}; + EXPECT_TRUE(value.is_nan()); + EXPECT_TRUE(value.is_signed()); + EXPECT_EQ(value.to_string(), "-sNaN123"); +} + +TEST(quiet_nan_payload_string) { + EXPECT_EQ(sourcemeta::core::Decimal::nan(456).to_string(), "NaN456"); +} + +TEST(big_coefficient_copy_assignment) { + const sourcemeta::core::Decimal source{"12345678901234567890123456789"}; + sourcemeta::core::Decimal target{"98765432109876543210987654321"}; + target = source; + EXPECT_EQ(target, source); + EXPECT_EQ(target.to_string(), "12345678901234567890123456789"); +} + +TEST(big_coefficient_copy_assignment_over_small) { + const sourcemeta::core::Decimal source{"12345678901234567890123456789"}; + sourcemeta::core::Decimal target{42}; + target = source; + EXPECT_EQ(target.to_string(), "12345678901234567890123456789"); +} + +TEST(engineering_string_of_zero_with_positive_exponent) { + const sourcemeta::core::Decimal value{"0E+5"}; + EXPECT_EQ(value.to_string(), "0.0e+6"); +} + +TEST(divide_integer_with_big_quotient) { + const sourcemeta::core::Decimal dividend{"100000000000000000000000000000"}; + const sourcemeta::core::Decimal divisor{7}; + EXPECT_EQ(dividend.divide_integer(divisor).to_string(), + "14285714285714285714285714285"); +} + +TEST(divide_integer_negative_big_quotient) { + const sourcemeta::core::Decimal dividend{"-100000000000000000000000000000"}; + const sourcemeta::core::Decimal divisor{7}; + EXPECT_EQ(dividend.divide_integer(divisor).to_string(), + "-14285714285714285714285714285"); +} + +TEST(subtract_with_borrow_cascade) { + const sourcemeta::core::Decimal left{"1000000000000000000000000000000"}; + const sourcemeta::core::Decimal right{1}; + EXPECT_EQ((left - right).to_string(), "1.0000000000000000e+30"); +} + +TEST(subtract_bigs_to_small_result) { + const sourcemeta::core::Decimal left{"12345678901234567890123456790"}; + const sourcemeta::core::Decimal right{"12345678901234567890123456789"}; + EXPECT_EQ((left - right).to_string(), "1"); +} + +TEST(compare_equal_length_bigs_differing_in_last_digit) { + const sourcemeta::core::Decimal left{"12345678901234567890123456788"}; + const sourcemeta::core::Decimal right{"12345678901234567890123456789"}; + EXPECT_LT(left, right); + EXPECT_FALSE(right < left); +} + +TEST(positive_exponent_to_uint64) { + const sourcemeta::core::Decimal value{"12E+3"}; + EXPECT_EQ(value.to_uint64(), 12000); +} + +TEST(positive_exponent_is_integral) { + const sourcemeta::core::Decimal value{"123E+2"}; + EXPECT_TRUE(value.is_integral()); +} + +TEST(multiply_bigs_with_carry) { + const sourcemeta::core::Decimal left{"99999999999999999999"}; + const sourcemeta::core::Decimal right{"99999999999999999999"}; + EXPECT_EQ((left * right).to_string(), "10.000000000000000e+39"); +} + +TEST(to_integral_of_big_fraction) { + const sourcemeta::core::Decimal value{"12345678901234567890123456789.5"}; + EXPECT_EQ(value.to_integral().to_string(), "12345678901234567890123456789"); +} + +TEST(remainder_of_sixteen_digit_dividend) { + EXPECT_EQ((sourcemeta::core::Decimal{"9999999999999999"} % + sourcemeta::core::Decimal{7}) + .to_string(), + "3"); +} + +TEST(divisible_by_at_seventeen_digits) { + EXPECT_TRUE(sourcemeta::core::Decimal{"99999999999999999"}.divisible_by( + sourcemeta::core::Decimal{3})); +} + +TEST(heap_coefficient_to_string_round_trip) { + const sourcemeta::core::Decimal value{ + "123456789012345678901234567890123456789012345"}; + EXPECT_EQ(value.to_string(), "123456789012345678901234567890123456789012345"); +} + +TEST(heap_coefficient_copy_assignment) { + const sourcemeta::core::Decimal source{ + "123456789012345678901234567890123456789012345"}; + sourcemeta::core::Decimal target{ + "987654321098765432109876543210987654321098765"}; + target = source; + EXPECT_EQ(target, source); + EXPECT_EQ(target.to_string(), + "123456789012345678901234567890123456789012345"); +} + +TEST(heap_coefficient_copy_assignment_over_small) { + const sourcemeta::core::Decimal source{ + "123456789012345678901234567890123456789012345"}; + sourcemeta::core::Decimal target{42}; + target = source; + EXPECT_EQ(target.to_string(), + "123456789012345678901234567890123456789012345"); +} + +TEST(heap_coefficient_copy_construction) { + const sourcemeta::core::Decimal source{ + "123456789012345678901234567890123456789012345"}; + const sourcemeta::core::Decimal copy{source}; + EXPECT_EQ(copy, source); + EXPECT_EQ(copy.to_string(), "123456789012345678901234567890123456789012345"); +} + +TEST(heap_coefficient_comparison) { + const sourcemeta::core::Decimal left{ + "12345678901234567890123456789012345678901234"}; + const sourcemeta::core::Decimal right{ + "98765432109876543210987654321098765432109876"}; + EXPECT_LT(left, right); + EXPECT_FALSE(right < left); + EXPECT_NE(left, right); +} + +TEST(heap_coefficient_divide_integer) { + const sourcemeta::core::Decimal dividend{ + "999999999999999999999999999999999999999999999"}; + const sourcemeta::core::Decimal divisor{7}; + EXPECT_EQ(dividend.divide_integer(divisor).to_string(), + "142857142857142857142857142857142857142857142"); +} + +TEST(heap_coefficient_negation) { + const sourcemeta::core::Decimal value{ + "123456789012345678901234567890123456789012345"}; + EXPECT_EQ((-value).to_string(), + "-123456789012345678901234567890123456789012345"); +} + +TEST(heap_coefficient_to_integral_truncates) { + const sourcemeta::core::Decimal value{ + "123456789012345678901234567890123456789012345.75"}; + EXPECT_EQ(value.to_integral().to_string(), + "123456789012345678901234567890123456789012345"); +} + +TEST(heap_coefficient_is_integral) { + const sourcemeta::core::Decimal value{ + "123456789012345678901234567890123456789012345"}; + EXPECT_TRUE(value.is_integral()); + const sourcemeta::core::Decimal fractional{ + "123456789012345678901234567890123456789012345.5"}; + EXPECT_FALSE(fractional.is_integral()); +} + +TEST(heap_coefficient_reduce) { + const sourcemeta::core::Decimal value{ + "123456789012345678901234567890123456789000000"}; + EXPECT_EQ(value.reduce().to_string(), + "123.456789012345678901234567890123456789e+42"); +} + +TEST(heap_subtraction_to_zero) { + const sourcemeta::core::Decimal value{ + "123456789012345678901234567890123456789012345"}; + EXPECT_EQ((value - value).to_string(), "0"); + EXPECT_TRUE((value - value).is_zero()); +} + +TEST(heap_move_assignment) { + sourcemeta::core::Decimal source{ + "123456789012345678901234567890123456789012345"}; + sourcemeta::core::Decimal target{ + "987654321098765432109876543210987654321098765"}; + target = std::move(source); + EXPECT_EQ(target.to_string(), + "123456789012345678901234567890123456789012345"); +} + +TEST(heap_move_construction) { + sourcemeta::core::Decimal source{ + "123456789012345678901234567890123456789012345"}; + const sourcemeta::core::Decimal target{std::move(source)}; + EXPECT_EQ(target.to_string(), + "123456789012345678901234567890123456789012345"); +} + +TEST(divide_integer_equal_word_length_operands) { + const sourcemeta::core::Decimal dividend{"123456789012345678901234567"}; + const sourcemeta::core::Decimal divisor{"123456789012345678901"}; + EXPECT_EQ(dividend.divide_integer(divisor).to_string(), "1000000"); +} + +TEST(divide_integer_shifted_estimate_small_quotient) { + const sourcemeta::core::Decimal dividend{ + "999999999999999999999999999999999999999999999"}; + const sourcemeta::core::Decimal divisor{ + "1000000000000000000000000000000000000000000"}; + EXPECT_EQ(dividend.divide_integer(divisor).to_string(), "999"); +} diff --git a/test/punycode/punycode_decode_test.cc b/test/punycode/punycode_decode_test.cc index bdcd980aa9..072f50990f 100644 --- a/test/punycode/punycode_decode_test.cc +++ b/test/punycode/punycode_decode_test.cc @@ -333,3 +333,30 @@ TEST(utf8_stream_api_with_non_ascii) { // Decodes to "München" in UTF-8 EXPECT_EQ(output.str(), "M\xC3\xBCnchen"); } + +TEST(error_decode_produces_invalid_code_point) { + try { + sourcemeta::core::punycode_to_utf32("999999v"); + FAIL(); + } catch (const sourcemeta::core::PunycodeError &error) { + EXPECT_STREQ(error.what(), "Invalid code point"); + } +} + +TEST(error_decode_weight_overflow) { + try { + sourcemeta::core::punycode_to_utf32(std::string(200, '9')); + FAIL(); + } catch (const sourcemeta::core::PunycodeError &error) { + EXPECT_STREQ(error.what(), "Decode overflow"); + } +} + +TEST(error_encode_invalid_utf8) { + try { + sourcemeta::core::utf8_to_punycode(std::string_view{"\xFF\xFE", 2}); + FAIL(); + } catch (const sourcemeta::core::PunycodeError &error) { + EXPECT_STREQ(error.what(), "Invalid UTF-8 input"); + } +} diff --git a/test/uri/uri_host_test.cc b/test/uri/uri_host_test.cc index 50fbad5760..5a5b2362a1 100644 --- a/test/uri/uri_host_test.cc +++ b/test/uri/uri_host_test.cc @@ -116,3 +116,15 @@ TEST(iri_unicode_host) { EXPECT_TRUE(uri.host().has_value()); EXPECT_EQ(uri.host().value(), "\xE4\xBE\x8B\xE3\x81\x88.jp"); } + +TEST(ipvfuture_host_is_preserved) { + const sourcemeta::core::URI uri{"http://[v1.fe80::a+en1]/path"}; + EXPECT_TRUE(uri.host().has_value()); + EXPECT_EQ(uri.host().value(), "v1.fe80::a+en1"); +} + +TEST(ipvfuture_host_uppercase_version) { + const sourcemeta::core::URI uri{"http://[vA.abc:def]"}; + EXPECT_TRUE(uri.host().has_value()); + EXPECT_EQ(uri.host().value(), "vA.abc:def"); +}