// Copyright 2026 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/browser/net/chrome_mojo_proxy_resolver_mac.h"

#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/time/time.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/browser/service_process_host.h"
#include "content/public/test/browser_test.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/proxy_resolution/proxy_list.h"
#include "services/proxy_resolver/public/mojom/proxy_resolver.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace {

constexpr char kTestUrl[] = "https://example.test/";

class ProxyResolverProcessObserver
    : public content::ServiceProcessHost::Observer {
 public:
  ProxyResolverProcessObserver() {
    content::ServiceProcessHost::AddObserver(this);
  }

  ProxyResolverProcessObserver(const ProxyResolverProcessObserver&) = delete;
  ProxyResolverProcessObserver& operator=(const ProxyResolverProcessObserver&) =
      delete;

  ~ProxyResolverProcessObserver() override {
    content::ServiceProcessHost::RemoveObserver(this);
  }

  bool is_service_running() const { return is_service_running_; }

  void WaitForLaunch() { launch_loop_.Run(); }
  void WaitForDeath() { death_loop_.Run(); }

 private:
  // content::ServiceProcessHost::Observer:
  void OnServiceProcessLaunched(
      const content::ServiceProcessInfo& info) override {
    if (!info.IsService<proxy_resolver::mojom::SystemProxyResolver>()) {
      return;
    }

    EXPECT_FALSE(is_service_running_);
    is_service_running_ = true;
    launch_loop_.Quit();
  }

  void OnServiceProcessTerminatedNormally(
      const content::ServiceProcessInfo& info) override {
    if (!info.IsService<proxy_resolver::mojom::SystemProxyResolver>()) {
      return;
    }

    EXPECT_TRUE(is_service_running_);
    is_service_running_ = false;
    death_loop_.Quit();
  }

 private:
  bool is_service_running_ = false;
  base::RunLoop launch_loop_;
  base::RunLoop death_loop_;
};

using ChromeMojoProxyResolverMacBrowserTest = InProcessBrowserTest;

// Ensures the proxy resolver service is started correctly and stopped when no
// resolvers are open.
IN_PROC_BROWSER_TEST_F(ChromeMojoProxyResolverMacBrowserTest,
                       ServiceLifecycle) {
  // Set up the system proxy resolver.
  mojo::Remote<proxy_resolver::mojom::SystemProxyResolver> proxy_resolver_mac(
      ChromeMojoProxyResolverMac::CreateWithSelfOwnedReceiverForTesting(
          base::TimeDelta()));

  ProxyResolverProcessObserver observer;

  // Attempt to resolve a proxy. This should create and start the service.
  base::RunLoop proxy_resolution_1;
  proxy_resolver_mac->GetProxyForUrl(
      GURL(kTestUrl),
      base::BindLambdaForTesting(
          [&](const net::ProxyList& proxy_list,
              proxy_resolver::mojom::SystemProxyResolutionStatusPtr status) {
            proxy_resolution_1.Quit();
          }));
  observer.WaitForLaunch();

  // Resolve another proxy. No new service should be created (the listener will
  // assert if that's the case).
  base::RunLoop proxy_resolution_2;
  proxy_resolver_mac->GetProxyForUrl(
      GURL(kTestUrl),
      base::BindLambdaForTesting(
          [&](const net::ProxyList& proxy_list,
              proxy_resolver::mojom::SystemProxyResolutionStatusPtr status) {
            proxy_resolution_2.Quit();
          }));
  EXPECT_TRUE(observer.is_service_running());

  // Wait for proxy resolution to complete. Once that's done, the service should
  // go away.
  proxy_resolution_1.Run();
  proxy_resolution_2.Run();
  observer.WaitForDeath();
}

// Same as above, but destroys the system proxy resolver, which should have
// no impact on service lifetime.
IN_PROC_BROWSER_TEST_F(ChromeMojoProxyResolverMacBrowserTest, DestroyResolver) {
  mojo::Remote<proxy_resolver::mojom::SystemProxyResolver> proxy_resolver_mac(
      ChromeMojoProxyResolverMac::CreateWithSelfOwnedReceiverForTesting(
          base::TimeDelta()));

  ProxyResolverProcessObserver observer;

  // Attempt to resolve a proxy. This should create and start the service.
  proxy_resolver_mac->GetProxyForUrl(
      GURL(kTestUrl),
      base::BindLambdaForTesting(
          [&](const net::ProxyList& proxy_list,
              proxy_resolver::mojom::SystemProxyResolutionStatusPtr status) {
            ADD_FAILURE() << "The GetProxyForURL callback should be dropped";
          }));
  observer.WaitForLaunch();

  // Destroy the resolver. The callback will never hit and the service should
  // eventually go away.
  proxy_resolver_mac.reset();
  EXPECT_TRUE(observer.is_service_running());
  observer.WaitForDeath();
}

// Make sure the service can be started again after it's been stopped.
IN_PROC_BROWSER_TEST_F(ChromeMojoProxyResolverMacBrowserTest,
                       DestroyAndCreateService) {
  mojo::Remote<proxy_resolver::mojom::SystemProxyResolver> proxy_resolver_mac(
      ChromeMojoProxyResolverMac::CreateWithSelfOwnedReceiverForTesting(
          base::TimeDelta()));

  ProxyResolverProcessObserver observer;

  // Attempt to resolve a proxy. This should create and start the service.
  base::RunLoop proxy_resolution_1;
  proxy_resolver_mac->GetProxyForUrl(
      GURL(kTestUrl),
      base::BindLambdaForTesting(
          [&](const net::ProxyList& proxy_list,
              proxy_resolver::mojom::SystemProxyResolutionStatusPtr status) {
            proxy_resolution_1.Quit();
          }));
  observer.WaitForLaunch();

  // Wait for proxy resolution to complete. Once that's done, the service should
  // go away.
  proxy_resolution_1.Run();
  observer.WaitForDeath();

  ProxyResolverProcessObserver observer2;
  // Attempt to resolve another proxy. This should recreate and start the
  // service.
  base::RunLoop proxy_resolution_2;
  proxy_resolver_mac->GetProxyForUrl(
      GURL(kTestUrl),
      base::BindLambdaForTesting(
          [&](const net::ProxyList& proxy_list,
              proxy_resolver::mojom::SystemProxyResolutionStatusPtr status) {
            proxy_resolution_2.Quit();
          }));
  observer2.WaitForLaunch();

  // Wait for proxy resolution to complete again. Once that's done, the service
  // should go away.
  proxy_resolution_2.Run();
  observer2.WaitForDeath();
}

}  // namespace
