#include "ada.h"
#include "gtest/gtest.h"
#include <cstdlib>
#include <iostream>

using Types = testing::Types<ada::url, ada::url_aggregator>;
template <class T>
struct basic_tests : testing::Test {};
TYPED_TEST_SUITE(basic_tests, Types);

TYPED_TEST(basic_tests, insane_url) {
  auto r = ada::parse<ada::url_aggregator>("e:@EEEEEEEEEE");
  ASSERT_TRUE(r);
  ASSERT_EQ(r->get_protocol(), "e:");
  ASSERT_EQ(r->get_username(), "");
  ASSERT_EQ(r->get_password(), "");
  ASSERT_EQ(r->get_hostname(), "");
  ASSERT_EQ(r->get_port(), "");
  ASSERT_EQ(r->get_pathname(), "@EEEEEEEEEE");
  SUCCEED();
}

TYPED_TEST(basic_tests, bad_percent_encoding) {
  auto r = ada::parse<TypeParam>("http://www.google.com/%X%");
  ASSERT_TRUE(r);
  ASSERT_EQ(r->get_href(), "http://www.google.com/%X%");
  r = ada::parse<TypeParam>("http://www.google%X%.com/");
  ASSERT_FALSE(r);
  r = ada::parse<TypeParam>("http://www.google.com/");
  ASSERT_TRUE(r);
  r->set_href("http://www.google.com/%X%");
  ASSERT_EQ(r->get_href(), "http://www.google.com/%X%");
  ASSERT_FALSE(r->set_host("www.google%X%.com"));
  SUCCEED();
}

TYPED_TEST(basic_tests, spaces_spaces) {
  auto r = ada::parse<TypeParam>("http://www.google.com/%37/ /");
  ASSERT_TRUE(r);
  ASSERT_EQ(r->get_href(), "http://www.google.com/%37/%20/");
  r->set_href("http://www.google.com/  /  /+/");
  ASSERT_TRUE(r);
  ASSERT_EQ(r->get_href(), "http://www.google.com/%20%20/%20%20/+/");
  r = ada::parse<TypeParam>("http://www.google com/");
  ASSERT_FALSE(r);
  SUCCEED();
}

TYPED_TEST(basic_tests, pluses) {
  auto r = ada::parse<TypeParam>("http://www.google.com/%37+/");
  ASSERT_TRUE(r);
  ASSERT_EQ(r->get_href(), "http://www.google.com/%37+/");
  r = ada::parse<TypeParam>("http://www.google+com/");
  ASSERT_TRUE(r);
  ASSERT_EQ(r->get_href(), "http://www.google+com/");
  SUCCEED();
}

TYPED_TEST(basic_tests, set_host_should_return_false_sometimes) {
  auto r = ada::parse<TypeParam>("mailto:a@b.com");
  ASSERT_FALSE(r->set_host("something"));
  auto r2 = ada::parse<TypeParam>("mailto:a@b.com");
  ASSERT_FALSE(r2->set_host("something"));
  SUCCEED();
}

TYPED_TEST(basic_tests, empty_url_should_return_false) {
  auto r = ada::parse<TypeParam>("");
  ASSERT_FALSE(r);
  SUCCEED();
}

TYPED_TEST(basic_tests, set_host_should_return_true_sometimes) {
  auto r = ada::parse<TypeParam>("https://www.google.com");
  ASSERT_TRUE(r->set_host("something"));
  SUCCEED();
}

TYPED_TEST(basic_tests, set_hostname_should_return_false_sometimes) {
  auto r = ada::parse<TypeParam>("mailto:a@b.com");
  ASSERT_FALSE(r->set_hostname("something"));
  SUCCEED();
}

TYPED_TEST(basic_tests, set_hostname_should_return_true_sometimes) {
  auto r = ada::parse<TypeParam>("https://www.google.com");
  ASSERT_TRUE(r->set_hostname("something"));
  SUCCEED();
}

TYPED_TEST(basic_tests, readme) {
  auto url = ada::parse<TypeParam>("https://www.google.com");
  ASSERT_TRUE(bool(url));
  SUCCEED();
}

TYPED_TEST(basic_tests, readmefree) {
  auto url = ada::parse("https://www.google.com");
  ASSERT_TRUE(bool(url));
  SUCCEED();
}

TYPED_TEST(basic_tests, readme2) {
  auto url = ada::parse<TypeParam>("https://www.google.com");
  url->set_username("username");
  url->set_password("password");
  ASSERT_EQ(url->get_href(), "https://username:password@www.google.com/");
  SUCCEED();
}

TYPED_TEST(basic_tests, readme2free) {
  auto url = ada::parse("https://www.google.com");
  url->set_username("username");
  url->set_password("password");
  ASSERT_EQ(url->get_href(), "https://username:password@www.google.com/");
  SUCCEED();
}

TYPED_TEST(basic_tests, readme3) {
  auto url = ada::parse<TypeParam>("https://www.google.com");
  ASSERT_EQ(url->set_protocol("wss"), true);
  ASSERT_EQ(url->get_protocol(), "wss:");
  ASSERT_EQ(url->get_href(), "wss://www.google.com/");
  SUCCEED();
}

TYPED_TEST(basic_tests, readme3free) {
  auto url = ada::parse("https://www.google.com");
  ASSERT_EQ(url->set_protocol("wss"), true);
  ASSERT_EQ(url->get_protocol(), "wss:");
  ASSERT_EQ(url->get_href(), "wss://www.google.com/");
  SUCCEED();
}

TYPED_TEST(basic_tests, set_protocol_should_return_false_sometimes) {
  auto url = ada::parse<TypeParam>("file:");
  ASSERT_EQ(url->set_protocol("https"), false);
  ASSERT_EQ(url->set_host("google.com"), true);
  ASSERT_EQ(url->get_href(), "file://google.com/");
  SUCCEED();
}

TYPED_TEST(basic_tests, set_protocol_should_return_true_sometimes) {
  auto url = ada::parse<TypeParam>("file:");
  ASSERT_EQ(url->set_host("google.com"), true);
  ASSERT_EQ(url->set_protocol("https"), true);
  ASSERT_EQ(url->get_href(), "https://google.com/");
  SUCCEED();
}

TYPED_TEST(basic_tests, readme4) {
  auto url = ada::parse<TypeParam>("https://www.google.com");
  url->set_host("github.com");
  ASSERT_EQ(url->get_host(), "github.com");
  SUCCEED();
}

TYPED_TEST(basic_tests, readme5) {
  auto url = ada::parse<TypeParam>("https://www.google.com");
  url->set_port("8080");
  ASSERT_EQ(url->get_port(), "8080");
  SUCCEED();
}

TYPED_TEST(basic_tests, readme6) {
  auto url = ada::parse<TypeParam>("https://www.google.com");
  url->set_pathname("/my-super-long-path");
  ASSERT_EQ(url->get_pathname(), "/my-super-long-path");
  SUCCEED();
}

TYPED_TEST(basic_tests, readme7) {
  auto url = ada::parse<TypeParam>("https://www.google.com");
  url->set_search("target=self");
  ASSERT_EQ(url->get_search(), "?target=self");
  SUCCEED();
}

TYPED_TEST(basic_tests, readme8) {
  auto url = ada::parse<TypeParam>("https://www.google.com");
  url->set_hash("is-this-the-real-life");
  ASSERT_EQ(url->get_hash(), "#is-this-the-real-life");
  SUCCEED();
}

TYPED_TEST(basic_tests, nodejs1) {
  auto base = ada::parse<TypeParam>("http://other.com/");
  ASSERT_TRUE(base.has_value());
  auto url = ada::parse<TypeParam>("http://GOOgoo.com", &base.value());
  ASSERT_TRUE(url.has_value());
  SUCCEED();
}

TYPED_TEST(basic_tests, nodejs2) {
  auto url = ada::parse<TypeParam>("data:space    ?test");
  ASSERT_EQ(url->get_search(), "?test");
  url->set_search("");
  ASSERT_EQ(url->get_search(), "");
  ASSERT_EQ(url->get_pathname(), "space   %20");
  ASSERT_EQ(url->get_href(), "data:space   %20");
  SUCCEED();
}

TYPED_TEST(basic_tests, nodejs3) {
  auto url = ada::parse<TypeParam>("data:space    ?test#test");
  ASSERT_EQ(url->get_search(), "?test");
  url->set_search("");
  ASSERT_EQ(url->get_search(), "");
  ASSERT_EQ(url->get_pathname(), "space   %20");
  ASSERT_EQ(url->get_href(), "data:space   %20#test");
  SUCCEED();
}

// https://github.com/nodejs/node/issues/46755
TYPED_TEST(basic_tests, nodejs4) {
  auto url = ada::parse<TypeParam>("file:///var/log/system.log");
  url->set_href("http://0300.168.0xF0");
  ASSERT_EQ(url->get_protocol(), "http:");
  ASSERT_EQ(url->get_href(), "http://192.168.0.240/");
  SUCCEED();
}

TYPED_TEST(basic_tests, empty_url) {
  auto url = ada::parse<TypeParam>("");
  ASSERT_FALSE(url);
  SUCCEED();
}

TYPED_TEST(basic_tests, just_hash) {
  auto url = ada::parse<TypeParam>("#x");
  ASSERT_FALSE(url);
  SUCCEED();
}

TYPED_TEST(basic_tests, empty_host_dash_dash_path) {
  auto url = ada::parse<TypeParam>("something:/.//");
  ASSERT_TRUE(url);
  ASSERT_FALSE(url->has_opaque_path);
  ASSERT_EQ(url->get_href(), "something:/.//");
  ASSERT_EQ(url->get_pathname(), "//");
  ASSERT_EQ(url->get_hostname(), "");
  SUCCEED();
}

TYPED_TEST(basic_tests, confusing_mess) {
  auto base_url = ada::parse<TypeParam>("http://example.org/foo/bar");
  ASSERT_TRUE(base_url);
  auto url = ada::parse<TypeParam>("http://::@c@d:2", &*base_url);
  ASSERT_TRUE(url);
  ASSERT_FALSE(url->has_opaque_path);
  ASSERT_EQ(url->get_hostname(), "d");
  ASSERT_EQ(url->get_host(), "d:2");
  ASSERT_EQ(url->get_pathname(), "/");
  ASSERT_EQ(url->get_href(), "http://:%3A%40c@d:2/");
  ASSERT_EQ(url->get_origin(), "http://d:2");
  SUCCEED();
}

TYPED_TEST(basic_tests, standard_file) {
  auto url = ada::parse<TypeParam>("file:///tmp/mock/path");
  ASSERT_TRUE(url);
  ASSERT_TRUE(url->has_empty_hostname());
  ASSERT_FALSE(url->has_opaque_path);
  ASSERT_EQ(url->get_pathname(), "/tmp/mock/path");
  ASSERT_EQ(url->get_hostname(), "");
  ASSERT_EQ(url->get_host(), "");
  ASSERT_EQ(url->get_href(), "file:///tmp/mock/path");
  SUCCEED();
}

TYPED_TEST(basic_tests, default_port_should_be_removed) {
  auto url = ada::parse<TypeParam>("http://www.google.com:443");
  ASSERT_TRUE(url);
  url->set_protocol("https");
  ASSERT_EQ(url->get_port(), "");
  ASSERT_EQ(url->get_host(), "www.google.com");
  SUCCEED();
}

TYPED_TEST(basic_tests, test_amazon) {
  auto url = ada::parse<TypeParam>("HTTP://AMAZON.COM");
  ASSERT_TRUE(url);
  ASSERT_EQ(url->get_href(), "http://amazon.com/");
  SUCCEED();
}

TYPED_TEST(basic_tests, remove_username) {
  auto url = ada::parse<TypeParam>("http://me@example.net");
  ASSERT_TRUE(url);
  url->set_username("");
  ASSERT_EQ(url->get_username(), "");
  ASSERT_EQ(url->get_href(), "http://example.net/");
  SUCCEED();
}

TYPED_TEST(basic_tests, remove_password) {
  auto url = ada::parse<TypeParam>("http://user:pass@example.net");
  ASSERT_TRUE(url);
  url->set_password("");
  ASSERT_EQ(url->get_password(), "");
  ASSERT_EQ(url->get_href(), "http://user@example.net/");
  SUCCEED();
}

TYPED_TEST(basic_tests, remove_password_with_empty_username) {
  auto url = ada::parse<TypeParam>("http://:pass@example.net");
  ASSERT_TRUE(url);
  url->set_password("");
  ASSERT_EQ(url->get_username(), "");
  ASSERT_EQ(url->get_password(), "");
  ASSERT_EQ(url->get_href(), "http://example.net/");
  SUCCEED();
}

TYPED_TEST(basic_tests, should_remove_dash_dot) {
  auto url = ada::parse<TypeParam>("non-spec:/.//p");
  ASSERT_TRUE(url);
  ASSERT_FALSE(url->has_empty_hostname());
  ASSERT_FALSE(url->has_hostname());
  url->set_hostname("h");
  ASSERT_TRUE(url->has_hostname());
  ASSERT_FALSE(url->has_empty_hostname());
  ASSERT_EQ(url->get_pathname(), "//p");
  ASSERT_EQ(url->get_href(), "non-spec://h//p");
  SUCCEED();
}

TYPED_TEST(basic_tests, should_remove_dash_dot_with_empty_hostname) {
  auto url = ada::parse<TypeParam>("non-spec:/.//p");
  ASSERT_TRUE(url);
  ASSERT_EQ(url->get_pathname(), "//p");
  ASSERT_FALSE(url->has_empty_hostname());
  ASSERT_FALSE(url->has_hostname());
  url->set_hostname("");
  ASSERT_TRUE(url->has_hostname());
  ASSERT_TRUE(url->has_empty_hostname());
  ASSERT_EQ(url->get_pathname(), "//p");
  ASSERT_EQ(url->get_href(), "non-spec:////p");
  SUCCEED();
}

TYPED_TEST(basic_tests, should_add_dash_dot_on_pathname) {
  auto url = ada::parse<TypeParam>("non-spec:/");
  ASSERT_TRUE(url);
  url->set_pathname("//p");
  ASSERT_EQ(url->get_pathname(), "//p");
  ASSERT_EQ(url->get_href(), "non-spec:/.//p");
  SUCCEED();
}

TYPED_TEST(basic_tests, should_update_password_correctly) {
  auto url = ada::parse<TypeParam>(
      "https://username:password@host:8000/path?query#fragment");
  ASSERT_TRUE(url);
  ASSERT_TRUE(url->set_password("test"));
  ASSERT_EQ(url->get_password(), "test");
  ASSERT_EQ(url->get_href(),
            "https://username:test@host:8000/path?query#fragment");
  SUCCEED();
}

// https://github.com/nodejs/node/issues/47889
TYPED_TEST(basic_tests, node_issue_47889) {
  auto urlbase = ada::parse<TypeParam>("a:b");
  ASSERT_EQ(urlbase->get_href(), "a:b");
  ASSERT_EQ(urlbase->get_protocol(), "a:");
  ASSERT_EQ(urlbase->get_pathname(), "b");
  ASSERT_TRUE(urlbase->has_opaque_path);
  ASSERT_TRUE(urlbase);
  auto expected_url = ada::parse<TypeParam>("a:b#");
  ASSERT_TRUE(expected_url);
  ASSERT_TRUE(expected_url->has_opaque_path);
  ASSERT_EQ(expected_url->get_href(), "a:b#");
  ASSERT_EQ(expected_url->get_pathname(), "b");
  auto url = ada::parse<TypeParam>("..#", &*urlbase);
  ASSERT_TRUE(url);
  ASSERT_TRUE(url->has_opaque_path);
  ASSERT_EQ(url->get_href(), "a:b/#");
  ASSERT_EQ(url->get_pathname(), "b/");
  SUCCEED();
}

TEST(basic_tests, can_parse) {
  ASSERT_TRUE(ada::can_parse("https://www.yagiz.co"));
  std::string_view base = "https://yagiz.co";
  ASSERT_TRUE(ada::can_parse("/hello", &base));

  std::string_view invalid_base = "!!!!!!!1";
  ASSERT_FALSE(ada::can_parse("/hello", &invalid_base));
  ASSERT_FALSE(ada::can_parse("!!!"));
  SUCCEED();
}

TYPED_TEST(basic_tests, node_issue_48254) {
  auto base_url = ada::parse<TypeParam>("localhost:80");
  ASSERT_TRUE(base_url);
  ASSERT_EQ(base_url->get_hostname(), "");
  ASSERT_EQ(base_url->get_host(), "");
  ASSERT_EQ(base_url->get_pathname(), "80");
  ASSERT_EQ(base_url->get_href(), "localhost:80");
  ASSERT_EQ(base_url->get_origin(), "null");
  ASSERT_EQ(base_url->has_opaque_path, true);
  auto url = ada::parse<TypeParam>("", &*base_url);
  ASSERT_FALSE(url);
  SUCCEED();
}

TYPED_TEST(basic_tests, url_host_type) {
  ASSERT_EQ(ada::parse<TypeParam>("http://localhost:3000")->host_type,
            ada::url_host_type::DEFAULT);
  ASSERT_EQ(ada::parse<TypeParam>("http://0.0.0.0")->host_type,
            ada::url_host_type::IPV4);
  ASSERT_EQ(
      ada::parse<TypeParam>("http://[2001:db8:3333:4444:5555:6666:7777:8888]")
          ->host_type,
      ada::url_host_type::IPV6);
  SUCCEED();
}

// https://github.com/nodejs/node/issues/49650
TYPED_TEST(basic_tests, nodejs_49650) {
  auto out = ada::parse<TypeParam>("http://foo");
  ASSERT_TRUE(out);
  ASSERT_FALSE(out->set_host("::"));
  ASSERT_EQ(out->get_href(), "http://foo/");
  SUCCEED();
}

// https://github.com/nodejs/node/issues/50235
TYPED_TEST(basic_tests, nodejs_50235) {
  auto out = ada::parse<TypeParam>("http://test.com:5/?param=1");
  ASSERT_TRUE(out);
  ASSERT_TRUE(out->set_pathname("path"));
  ASSERT_EQ(out->get_href(), "http://test.com:5/path?param=1");
  SUCCEED();
}

// https://github.com/nodejs/node/issues/51514
TYPED_TEST(basic_tests, nodejs_51514) {
  auto out = ada::parse<TypeParam>("http://1.1.1.256");
  ASSERT_FALSE(out);
}

// https://github.com/nodejs/node/issues/51593
TYPED_TEST(basic_tests, nodejs_51593) {
  auto out = ada::parse<TypeParam>("http://\u200b123.123.123.123");
  ASSERT_TRUE(out);
  ASSERT_EQ(out->get_href(), "http://123.123.123.123/");
  SUCCEED();
}

// https://github.com/nodejs/node/issues/51619
TYPED_TEST(basic_tests, nodejs_51619) {
  auto out = ada::parse<TypeParam>("https://0.0.0.0x100/");
  ASSERT_FALSE(out);
  SUCCEED();
}

// https://github.com/nodejs/undici/pull/2971
TYPED_TEST(basic_tests, nodejs_undici_2971) {
  std::string_view base =
      "https://non-ascii-location-header.sys.workers.dev/redirect";
  auto base_url = ada::parse<TypeParam>(base);
  ASSERT_TRUE(base_url);
  auto out = ada::parse<TypeParam>("/\xec\x95\x88\xeb\x85\x95", &*base_url);
  ASSERT_TRUE(out);
  ASSERT_EQ(
      out->get_href(),
      R"(https://non-ascii-location-header.sys.workers.dev/%EC%95%88%EB%85%95)");
  SUCCEED();
}

TYPED_TEST(basic_tests, path_setter_bug) {
  std::string_view base = "blob:/?";
  auto base_url = ada::parse<ada::url_aggregator>(base);
  ASSERT_TRUE(base_url);
  ASSERT_TRUE(base_url->validate());
  ASSERT_TRUE(base_url->set_pathname("//.."));
  ASSERT_TRUE(base_url->validate());
  SUCCEED();
}

TYPED_TEST(basic_tests, negativeport) {
  auto url = ada::parse<TypeParam>("https://www.google.com");
  ASSERT_FALSE(url->set_port("-1"));
  SUCCEED();
}

// https://github.com/ada-url/ada/issues/826
TYPED_TEST(basic_tests, set_invalid_port) {
  auto url = ada::parse<TypeParam>("fake://dummy.test");
  ASSERT_TRUE(url);
  ASSERT_FALSE(url->set_port("invalid80"));
  ASSERT_EQ(url->get_port(), "");
  ASSERT_TRUE(url->set_port("80valid"));
  ASSERT_TRUE(url->is_valid);
  ASSERT_EQ(url->get_port(), "80");
  ASSERT_TRUE(url->is_valid);
  SUCCEED();
}

TYPED_TEST(basic_tests, test_possible_asan) {
  auto url = ada::parse<TypeParam>("file:///");
  ASSERT_TRUE(url);
  ASSERT_EQ(url->get_protocol(), "file:");
  SUCCEED();
}

TYPED_TEST(basic_tests, test_issue_935) {
  auto url = ada::parse<TypeParam>("file:///foo/.bar/../baz.js");
  ASSERT_TRUE(url);
  ASSERT_EQ(url->get_pathname(), "/foo/baz.js");

  // this should go into the fast path also
  auto no_dot = ada::parse<TypeParam>("file:///foo/bar/baz.js");
  ASSERT_EQ(no_dot->get_pathname(), "/foo/bar/baz.js");
  SUCCEED();
}

TYPED_TEST(basic_tests, test_issue_970) {
  auto url = ada::parse<TypeParam>("http://foo/bar^baz");
  ASSERT_TRUE(url);
  ASSERT_EQ(url->get_pathname(), "/bar%5Ebaz");
  SUCCEED();
}

// Ref: https://github.com/cloudflare/workerd/issues/5144
TYPED_TEST(basic_tests, test_workerd_issue_5144_1) {
  auto url = ada::parse<TypeParam>("https://example.sub.com/??");
  ASSERT_TRUE(url);
  ASSERT_EQ(url->get_search(), "??");
  ASSERT_EQ(url->get_href(), "https://example.sub.com/??");

  SUCCEED();
}

// Ref: https://github.com/cloudflare/workerd/issues/5144
TYPED_TEST(basic_tests, test_workerd_issue_5144_2) {
  auto url = ada::parse<TypeParam>("https://example.sub.com/???");
  ASSERT_TRUE(url);
  ASSERT_EQ(url->get_search(), "???");
  ASSERT_EQ(url->get_href(), "https://example.sub.com/???");
  SUCCEED();
}

// Ref: https://github.com/cloudflare/workerd/issues/5144
TYPED_TEST(basic_tests, test_workerd_issue_5144_3) {
  auto url = ada::parse<TypeParam>("https://example.sub.com/????");
  ASSERT_TRUE(url);
  ASSERT_EQ(url->get_search(), "????");
  ASSERT_EQ(url->get_href(), "https://example.sub.com/????");
  SUCCEED();
}

// Ref: https://github.com/cloudflare/workerd/issues/5144
TYPED_TEST(basic_tests, test_workerd_issue_5144_4) {
  using regex_provider = ada::url_pattern_regex::std_regex_provider;
  auto init = ada::url_pattern_init{};
  init.hostname = ":subdomain.:domain.:tld";
  auto pattern = ada::parse_url_pattern<regex_provider>(init);
  ASSERT_TRUE(pattern);
  ASSERT_TRUE(pattern->match("https://example.com"));
  ASSERT_TRUE(pattern->match("https://example.com/?"));
  ASSERT_TRUE(pattern->match("https://example.com/??"));

  auto dummy_init = ada::url_pattern_init{};
  dummy_init.search = "???";
  ASSERT_TRUE(pattern->exec(std::move(dummy_init)));
  SUCCEED();
}

// https://github.com/ada-url/ada/issues/1076
// Setting pathname to a "//" path on a non-special URL without authority but
// with a query or hash component should not trigger a validate() assertion
// failure caused by stale search_start/hash_start offsets after "/." insertion.
TEST(basic_tests, issue_1076_set_pathname_dashdot_with_query) {
  // Non-special URL with a query: no authority, has search component
  auto url = ada::parse<ada::url_aggregator>("foo:/?q");
  ASSERT_TRUE(url);
  ASSERT_TRUE(url->validate());
  ASSERT_TRUE(url->set_pathname("//bar"));
  ASSERT_TRUE(url->validate());
  ASSERT_EQ(url->get_pathname(), "//bar");
  ASSERT_EQ(url->get_search(), "?q");
  SUCCEED();
}

TEST(basic_tests, issue_1076_set_pathname_dashdot_with_hash) {
  // Non-special URL with a hash: no authority, has hash component
  auto url = ada::parse<ada::url_aggregator>("foo:/#h");
  ASSERT_TRUE(url);
  ASSERT_TRUE(url->validate());
  ASSERT_TRUE(url->set_pathname("//bar"));
  ASSERT_TRUE(url->validate());
  ASSERT_EQ(url->get_pathname(), "//bar");
  ASSERT_EQ(url->get_hash(), "#h");
  SUCCEED();
}

TEST(basic_tests, issue_1076_set_pathname_dashdot_with_query_and_hash) {
  // Non-special URL with both query and hash
  auto url = ada::parse<ada::url_aggregator>("foo:/?q#h");
  ASSERT_TRUE(url);
  ASSERT_TRUE(url->validate());
  ASSERT_TRUE(url->set_pathname("//bar"));
  ASSERT_TRUE(url->validate());
  ASSERT_EQ(url->get_pathname(), "//bar");
  ASSERT_EQ(url->get_search(), "?q");
  ASSERT_EQ(url->get_hash(), "#h");
  SUCCEED();
}

TEST(basic_tests, issue_1076_blob_with_query) {
  // blob: scheme with query - similar to existing path_setter_bug test
  auto url = ada::parse<ada::url_aggregator>("blob:/?q");
  ASSERT_TRUE(url);
  ASSERT_TRUE(url->validate());
  ASSERT_TRUE(url->set_pathname("//p"));
  ASSERT_TRUE(url->validate());
  ASSERT_EQ(url->get_pathname(), "//p");
  ASSERT_EQ(url->get_search(), "?q");
  SUCCEED();
}

TEST(basic_tests, issue_1076_setter_sequence) {
  // Simulates the fuzzer scenario: parse a URL, then call multiple setters
  // to put it into a vulnerable state before set_pathname
  auto url = ada::parse<ada::url_aggregator>("foo://host/path?query#hash");
  ASSERT_TRUE(url);
  ASSERT_TRUE(url->validate());
  // Clear the host to remove authority
  url->set_hostname("");
  url->set_host("");
  ASSERT_TRUE(url->validate());
  // Now set pathname to something starting with //
  ASSERT_TRUE(url->set_pathname("//newpath"));
  ASSERT_TRUE(url->validate());
  SUCCEED();
}

// Regression test: parsing an empty string or no-scheme input relative to a
// base URL with an empty query string ("?") must preserve the empty query in
// both ada::url and ada::url_aggregator. Previously, url_aggregator would
// drop the query entirely because update_base_search("") incorrectly cleared
// it instead of preserving the "?" marker.
TEST(basic_tests, empty_query_base_consistency) {
  // FILE state: empty source against file:// base with empty query.
  {
    auto bu = ada::parse<ada::url>("file:///path?");
    auto ba = ada::parse<ada::url_aggregator>("file:///path?");
    ASSERT_TRUE(bu);
    ASSERT_TRUE(ba);
    auto ru = ada::parse<ada::url>("", &*bu);
    auto ra = ada::parse<ada::url_aggregator>("", &*ba);
    ASSERT_TRUE(ru);
    ASSERT_TRUE(ra);
    EXPECT_EQ(ru->get_href(), std::string(ra->get_href()));
    EXPECT_TRUE(ru->has_search());
    EXPECT_TRUE(ra->has_search());
    EXPECT_EQ(ru->get_search(), "");
    EXPECT_EQ(std::string(ra->get_search()), "");
  }

  // FILE state: original fuzzer crash input.
  {
    auto bu = ada::parse<ada::url>("file://e//.U./UU.//&eSe?");
    auto ba = ada::parse<ada::url_aggregator>("file://e//.U./UU.//&eSe?");
    ASSERT_TRUE(bu);
    ASSERT_TRUE(ba);
    auto ru = ada::parse<ada::url>("", &*bu);
    auto ra = ada::parse<ada::url_aggregator>("", &*ba);
    ASSERT_TRUE(ru);
    ASSERT_TRUE(ra);
    EXPECT_EQ(ru->get_href(), std::string(ra->get_href()));
  }

  // RELATIVE_SCHEME state: empty source against https:// base with empty query.
  {
    auto bu = ada::parse<ada::url>("https://example.com/path?");
    auto ba = ada::parse<ada::url_aggregator>("https://example.com/path?");
    ASSERT_TRUE(bu);
    ASSERT_TRUE(ba);
    auto ru = ada::parse<ada::url>("", &*bu);
    auto ra = ada::parse<ada::url_aggregator>("", &*ba);
    ASSERT_TRUE(ru);
    ASSERT_TRUE(ra);
    EXPECT_EQ(ru->get_href(), std::string(ra->get_href()));
    EXPECT_TRUE(ru->has_search());
    EXPECT_TRUE(ra->has_search());
  }

  // NO_SCHEME state: fragment-only input against opaque-path base with empty
  // query.
  {
    auto bu = ada::parse<ada::url>("foo:bar?");
    auto ba = ada::parse<ada::url_aggregator>("foo:bar?");
    ASSERT_TRUE(bu);
    ASSERT_TRUE(ba);
    auto ru = ada::parse<ada::url>("#hash", &*bu);
    auto ra = ada::parse<ada::url_aggregator>("#hash", &*ba);
    ASSERT_TRUE(ru);
    ASSERT_TRUE(ra);
    EXPECT_EQ(ru->get_href(), std::string(ra->get_href()));
  }
}

// Regression test: canonicalize_pathname with path traversal that reduces
// the normalized pathname to fewer than 2 characters must not throw
// std::out_of_range. Previously, "fake://fake-url/-../../" normalized to
// pathname "/" (1 char) and the code called pathname.substr(2) which threw.
#if ADA_INCLUDE_URL_PATTERN
TEST(basic_tests, url_pattern_canonicalize_pathname_traversal) {
  using regex_provider = ada::url_pattern_regex::std_regex_provider;
  // These inputs have non-leading-slash pathnames that, after URL
  // normalization of path traversal sequences, produce a pathname shorter
  // than 2 characters.  They must return a failure (not crash).
  ada::url_pattern_init init1{};
  init1.pathname = "../../";
  auto result1 =
      ada::parse_url_pattern<regex_provider>(init1, nullptr, nullptr);
  // Result may be success or failure, but must not crash.
  (void)result1;

  ada::url_pattern_init init2{};
  init2.pathname = "../";
  auto result2 =
      ada::parse_url_pattern<regex_provider>(init2, nullptr, nullptr);
  (void)result2;

  // A simple relative pathname (no traversal) exercises the
  // !leading_slash && pathname.size() >= 2 branch (returns substr(2)).
  ada::url_pattern_init init3{};
  init3.pathname = "simple";
  auto result3 =
      ada::parse_url_pattern<regex_provider>(init3, nullptr, nullptr);
  (void)result3;

  SUCCEED();
}
#endif  // ADA_INCLUDE_URL_PATTERN
