// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/renderer/controlled_frame/web_url_pattern_natives.h"

#include <optional>
#include <vector>

#include "base/containers/to_value_list.h"
#include "base/test/test_future.h"
#include "chrome/test/base/chrome_render_view_test.h"
#include "extensions/common/mojom/context_type.mojom.h"
#include "extensions/common/mojom/host_id.mojom.h"
#include "extensions/renderer/module_system.h"
#include "extensions/renderer/script_context.h"
#include "extensions/renderer/string_source_map.h"
#include "v8/include/v8.h"

namespace controlled_frame {

namespace {
v8::Local<v8::String> ToV8String(const char* str) {
  return v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), str)
      .ToLocalChecked();
}
}  // namespace

class WebUrlPatternNativesBrowserTest : public ChromeRenderViewTest {
 public:
  void SetUp() override {
    ChromeRenderViewTest::SetUp();

    v8::HandleScope handle_scope(Isolate());
    v8::Local<v8::Context> context = v8::Context::New(Isolate());
    v8::Context::Scope context_scope(context);
    script_context_ = std::make_unique<extensions::ScriptContext>(
        context, /* frame=*/nullptr,
        extensions::mojom::HostID(
            extensions::mojom::HostID::HostType::kControlledFrameEmbedder,
            "isolated-app://some_app_id"),
        /* extension=*/nullptr,
        /* blink_isolated_world_id=*/std::nullopt,
        extensions::mojom::ContextType::kWebPage,
        /* effective_extension=*/nullptr,
        extensions::mojom::ContextType::kWebPage);
    script_context_->SetModuleSystem(std::make_unique<extensions::ModuleSystem>(
        script_context_.get(), &source_map_));

    script_context_->module_system()->RegisterNativeHandler(
        "WebUrlPatternNatives",
        std::make_unique<controlled_frame::WebUrlPatternNatives>(
            script_context_.get()));
  }

  void TearDown() override {
    script_context_->Invalidate();
    ChromeRenderViewTest::TearDown();
  }

 protected:
  void RegisterAndRequireTestModule(const std::string& module_name,
                                    const std::string& module_source) {
    extensions::ModuleSystem::NativesEnabledScope enable_natives(
        script_context_->module_system());
    source_map_.RegisterModule(module_name, module_source);
    ASSERT_FALSE(script_context_->module_system()
                     ->Require(module_name)
                     .ToLocalChecked()
                     .IsEmpty());
  }

  std::optional<base::Value> CallModuleMethod(
      const std::string& module_name,
      const std::string& method_name,
      std::vector<v8::Local<v8::Value>>* args) {
    base::test::TestFuture<std::optional<base::Value>, base::TimeTicks> future;
    script_context_->module_system()->CallModuleMethodSafe(
        module_name, method_name, args->size(), args->data(),
        future.GetCallback());
    return std::get<0>(future.Take());
  }

 private:
  std::unique_ptr<extensions::ScriptContext> script_context_;
  extensions::StringSourceMap source_map_;
};

TEST_F(WebUrlPatternNativesBrowserTest, URLPatternToMatchPatterns) {
  v8::HandleScope handle_scope(Isolate());
  RegisterAndRequireTestModule("TestRunner", R"(
    const natives = requireNative('WebUrlPatternNatives');

    exports.runTest = function(urlPatternString) {
      try {
        return natives.URLPatternToMatchPatterns(urlPatternString);
      } catch(e) {
        return [];
      }
    })");

  struct URLPatternTestCase {
    std::string input_url_pattern;
    std::vector<std::string> expected_match_patterns;
  };

  const URLPatternTestCase kTestCases[] = {
      {"http://*/*", {"http://*/*", "http://*/*?*"}},
      {"*://*/*",
       {"*://*/*", "*://*/*?*", "ws://*/*", "wss://*/*", "ws://*/*?*",
        "wss://*/*?*"}},
      {"*://asdf.com/*",
       {"*://asdf.com/*", "*://asdf.com/*?*", "ws://asdf.com/*",
        "wss://asdf.com/*", "ws://asdf.com/*?*", "wss://asdf.com/*?*"}},
      {"https://*.asdf.com/fdsa",
       {"https://*.asdf.com/fdsa", "https://*.asdf.com/fdsa?*"}},
      {"ws://*.asdf.com/*", {"ws://*.asdf.com/*", "ws://*.asdf.com/*?*"}},
      {"wss://*/asdf/*/fdsa", {"wss://*/asdf/*/fdsa", "wss://*/asdf/*/fdsa?*"}},
      {"https://*", {"https://*/*", "https://*/*?*"}},
      {"https://*/", {"https://*/", "https://*/?*"}},
      {"https://*/asdf", {"https://*/asdf", "https://*/asdf?*"}},
      {"http://asdf.com", {"http://asdf.com/*", "http://asdf.com/*?*"}},
      {"http://asdf.com/fdsa",
       {"http://asdf.com/fdsa", "http://asdf.com/fdsa?*"}},
      {"http://asdf.com/fdsa/*",
       {"http://asdf.com/fdsa/*", "http://asdf.com/fdsa/*?*"}},
      {"http://asdf.com*", {}},  // only * or *.something in the domain
      {"ftp://asdf.com/", {}},   // no ftp
      {"asdf://asdf.com/", {}},  // wrong protocol
      {"http://*.asdf.com/", {"http://*.asdf.com/", "http://*.asdf.com/?*"}},
      {"http://*asdf.com/", {}},   // only * or *.something in the domain
      {"http://asdf.*.com/", {}},  // only * or *.something in the domain
      {"http:/asdf.com", {}},      // malformed protocol part
      {"http://asdf.com:8080/fdsa",
       {"http://asdf.com:8080/fdsa", "http://asdf.com:8080/fdsa?*"}},
      {"*://asdf.com:8080/fdsa", {}},
      {"http://asdf.com:80*/fdsa", {}},
      {"http://asdf.com:*80/fdsa", {}},
      {"*://asdf.com:*/fdsa",
       {"*://asdf.com:*/fdsa", "*://asdf.com:*/fdsa?*", "ws://asdf.com:*/fdsa",
        "wss://asdf.com:*/fdsa", "ws://asdf.com:*/fdsa?*",
        "wss://asdf.com:*/fdsa?*"}},
      {"http://asdf.com:*/fdsa",
       {"http://asdf.com:*/fdsa", "http://asdf.com:*/fdsa?*"}},
      {"http://asdf.com:fdsa/asdf", {}},
      {"http://asdf.com/fdsa?query", {"http://asdf.com/fdsa?query"}},
      {"http://asdf.com/?query=fdsa&query2=asdf",
       {"http://asdf.com/?query=fdsa&query2=asdf"}},
      {"http://asdf.com?query", {"http://asdf.com/?query"}},
      {"http://user@asdf.com/fdsa", {}},
      {"http://user:pass@asdf.com/fdsa", {}},
      {"http://asdf.com/:group", {}},
      {"http://:asdf.com/", {}},
  };

  for (const auto& test : kTestCases) {
    std::vector<v8::Local<v8::Value>> args = {
        ToV8String(test.input_url_pattern.c_str())};
    std::optional<base::Value> match_patterns =
        CallModuleMethod("TestRunner", "runTest", &args);

    ASSERT_TRUE(match_patterns.has_value());
    EXPECT_EQ(match_patterns.value(),
              base::ToValueList(test.expected_match_patterns));
  }
}

}  // namespace controlled_frame
