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

#include "base/fuchsia/scoped_service_binding.h"

#include <lib/async/default.h>
#include <lib/sys/cpp/component_context.h>

#include "base/fuchsia/process_context.h"
#include "base/fuchsia/test_component_context_for_process.h"
#include "base/fuchsia/test_interface_natural_impl.h"
#include "base/run_loop.h"
#include "base/strings/string_piece.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {

class ScopedNaturalServiceBindingTest : public testing::Test {
 protected:
  ScopedNaturalServiceBindingTest() = default;
  ~ScopedNaturalServiceBindingTest() override = default;

  const base::test::SingleThreadTaskEnvironment task_environment_{
      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};

  TestComponentContextForProcess test_context_;
  TestInterfaceNaturalImpl test_service_;
};

// Verifies that ScopedNaturalServiceBinding allows more than one simultaneous
// client.
TEST_F(ScopedNaturalServiceBindingTest, ConnectTwice) {
  ScopedNaturalServiceBinding<base_testfidl::TestInterface> binding(
      ComponentContextForProcess()->outgoing().get(), &test_service_);

  auto stub =
      CreateTestInterfaceClient(test_context_.published_services_natural());
  auto stub2 =
      CreateTestInterfaceClient(test_context_.published_services_natural());
  EXPECT_EQ(VerifyTestInterface(stub), ZX_OK);
  EXPECT_EQ(VerifyTestInterface(stub2), ZX_OK);
}

// Verifies that ScopedNaturalServiceBinding allows more than one simultaneous
// client with a non-default discovery name.
TEST_F(ScopedNaturalServiceBindingTest, ConnectTwiceNameOverride) {
  const char kInterfaceName[] = "fuchsia.TestInterface2";

  ScopedNaturalServiceBinding<base_testfidl::TestInterface> new_service_binding(
      ComponentContextForProcess()->outgoing().get(), &test_service_,
      kInterfaceName);

  auto stub = CreateTestInterfaceClient(
      test_context_.published_services_natural(), kInterfaceName);
  auto stub2 = CreateTestInterfaceClient(
      test_context_.published_services_natural(), kInterfaceName);
  EXPECT_EQ(VerifyTestInterface(stub), ZX_OK);
  EXPECT_EQ(VerifyTestInterface(stub2), ZX_OK);
}

// Verify that we can publish a debug `TestInterface` service.
TEST_F(ScopedNaturalServiceBindingTest, ConnectDebugService) {
  vfs::PseudoDir* const debug_dir =
      ComponentContextForProcess()->outgoing()->debug_dir();

  // Publish the test service to the "debug" directory.
  ScopedNaturalServiceBinding<base_testfidl::TestInterface>
      debug_service_binding(debug_dir, &test_service_);

  // Connect a `ClientEnd` to the "debug" subdirectory.
  auto debug_directory_endpoints =
      fidl::CreateEndpoints<fuchsia_io::Directory>();
  ASSERT_TRUE(debug_directory_endpoints.is_ok())
      << debug_directory_endpoints.status_string();
  debug_dir->Serve(fuchsia::io::OpenFlags::RIGHT_READABLE |
                       fuchsia::io::OpenFlags::RIGHT_WRITABLE,
                   debug_directory_endpoints->server.TakeChannel());

  // Attempt to connect via the "debug" directory.
  auto debug_stub =
      CreateTestInterfaceClient(std::move(debug_directory_endpoints->client));
  EXPECT_EQ(VerifyTestInterface(debug_stub), ZX_OK);

  // Verify that the `TestInterface` service does not appear in the outgoing
  // service directory.
  auto release_stub =
      CreateTestInterfaceClient(test_context_.published_services_natural());
  EXPECT_EQ(VerifyTestInterface(release_stub), ZX_ERR_PEER_CLOSED);
}

// Test the last client callback is called every time the number of active
// clients reaches 0.
TEST_F(ScopedNaturalServiceBindingTest, MultipleLastClientCallback) {
  ScopedNaturalServiceBinding<base_testfidl::TestInterface> binding(
      ComponentContextForProcess()->outgoing().get(), &test_service_);
  int disconnect_count = 0;
  binding.SetOnLastClientCallback(
      BindLambdaForTesting([&disconnect_count]() { ++disconnect_count; }));

  // Connect a client, verify it is functional.
  {
    auto stub =
        CreateTestInterfaceClient(test_context_.published_services_natural());
    EXPECT_EQ(VerifyTestInterface(stub), ZX_OK);
  }

  // Client disconnected on going out of scope, the callback should have been
  // called once.
  RunLoop().RunUntilIdle();
  EXPECT_EQ(disconnect_count, 1);

  // Connect another client, verify it is functional.
  {
    auto stub =
        CreateTestInterfaceClient(test_context_.published_services_natural());
    EXPECT_EQ(VerifyTestInterface(stub), ZX_OK);
  }

  // Client disconnected on going out of scope, the callback should have been
  // called a second time.
  RunLoop().RunUntilIdle();
  EXPECT_EQ(disconnect_count, 2);
}

// Test the last client callback is called every time the number of active
// clients reaches 0.
TEST_F(ScopedNaturalServiceBindingTest, LastClientCallbackOnlyForLastClient) {
  ScopedNaturalServiceBinding<base_testfidl::TestInterface> binding(
      ComponentContextForProcess()->outgoing().get(), &test_service_);
  int disconnect_count = 0;
  binding.SetOnLastClientCallback(
      BindLambdaForTesting([&disconnect_count]() { ++disconnect_count; }));

  {
    // Connect a long lived client, verify it is functional.
    auto long_lived_stub =
        CreateTestInterfaceClient(test_context_.published_services_natural());
    EXPECT_EQ(VerifyTestInterface(long_lived_stub), ZX_OK);

    // Connect a client, verify it is functional.
    {
      auto stub =
          CreateTestInterfaceClient(test_context_.published_services_natural());
      EXPECT_EQ(VerifyTestInterface(stub), ZX_OK);
    }

    // Client disconnected on going out of scope, the callback should not have
    // been called because the long-lived client is still connected.
    RunLoop().RunUntilIdle();
    EXPECT_EQ(disconnect_count, 0);

    // Connect another client, verify it is functional.
    {
      auto stub =
          CreateTestInterfaceClient(test_context_.published_services_natural());
      EXPECT_EQ(VerifyTestInterface(stub), ZX_OK);
    }

    // Client disconnected on going out of scope, the callback should not have
    // been called because the long-lived client is still connected.
    RunLoop().RunUntilIdle();
    EXPECT_EQ(disconnect_count, 0);
  }

  // Long lived client disconnected on going out of scope, the callback should
  // have been called a third time.
  RunLoop().RunUntilIdle();
  EXPECT_EQ(disconnect_count, 1);
}

}  // namespace base
