#include <fuchsia/modular/testing/cpp/fidl.h>
#include <lib/modular_test_harness/cpp/test_harness_fixture.h>

#include "fuchsia/modular/session/cpp/fidl.h"

namespace {

const std::string kTestAgentUrl(
    "fuchsia-pkg:
const std::string kTestServiceName(fuchsia::modular::Clipboard::Name_);

struct ConnectToAgentServiceTestConfig {

  std::map<std::string, std::string> service_to_agent_map;

  bool provide_service_name = false;

  bool provide_handler = false;

  bool provide_channel = false;

  bool provide_agent_controller = false;

  template <typename Interface>
  fuchsia::modular::AgentServiceRequest MakeAgentServiceRequest(
      std::string service_name, Interface service_request,
      fidl::InterfaceRequest<fuchsia::modular::AgentController> agent_controller) {
    fuchsia::modular::AgentServiceRequest agent_service_request;
    if (provide_service_name) {
      agent_service_request.set_service_name(service_name);
    }
    if (provide_handler) {
      agent_service_request.set_handler(kTestAgentUrl);
    }
    if (provide_channel) {
      agent_service_request.set_channel(service_request.TakeChannel());
    }
    if (provide_agent_controller) {
      agent_service_request.set_agent_controller(std::move(agent_controller));
    }
    return agent_service_request;
  }
};

struct ConnectToAgentServiceExpect {

  bool got_peek_content = false;

  zx_status_t service_status = ZX_OK;
};

class AgentServicesTest : public modular::testing::TestHarnessFixture {
 protected:
  fuchsia::modular::ComponentContextPtr StartTestHarness(
      ConnectToAgentServiceTestConfig test_config) {
    fuchsia::modular::testing::TestHarnessSpec spec;

    std::vector<fuchsia::modular::session::AgentServiceIndexEntry> agent_service_index;

    for (const auto& entry : test_config.service_to_agent_map) {
      fuchsia::modular::session::AgentServiceIndexEntry agent_service;
      agent_service.set_service_name(entry.first);
      agent_service.set_agent_url(entry.second);
      agent_service_index.emplace_back(std::move(agent_service));
    }

    spec.mutable_sessionmgr_config()->set_agent_service_index(std::move(agent_service_index));

    test_harness()->Run(std::move(spec));

    fuchsia::modular::ComponentContextPtr component_context;
    fuchsia::modular::testing::ModularService modular_service;
    modular_service.set_component_context(component_context.NewRequest());
    test_harness()->ConnectToModularService(std::move(modular_service));

    return component_context;
  }

  void ExecuteConnectToAgentServiceTest(ConnectToAgentServiceTestConfig test_config,
                                        ConnectToAgentServiceExpect expect) {
    const std::string kTestContent("Test clipboard content");

    fuchsia::modular::ComponentContextPtr component_context = StartTestHarness(test_config);

    fuchsia::modular::ClipboardPtr clipboard_service_ptr;
    auto service_name = clipboard_service_ptr->Name_;
    auto service_request = clipboard_service_ptr.NewRequest();
    zx_status_t service_status = ZX_OK;
    clipboard_service_ptr.set_error_handler(
        [&service_status](zx_status_t status) { service_status = status; });

    fuchsia::modular::AgentControllerPtr agent_controller;
    zx_status_t agent_controller_status = ZX_OK;
    agent_controller.set_error_handler(
        [&agent_controller_status](zx_status_t status) { agent_controller_status = status; });

    auto agent_service_request = test_config.MakeAgentServiceRequest(
        service_name, std::move(service_request), agent_controller.NewRequest());
    component_context->ConnectToAgentService(std::move(agent_service_request));

    clipboard_service_ptr->Push(kTestContent);
    bool got_peek_content = false;
    clipboard_service_ptr->Peek([&](std::string content) {
      got_peek_content = true;
      EXPECT_EQ(content, kTestContent);
    });

    RunLoopUntil([&] {
      return got_peek_content || service_status != ZX_OK ||
             (expect.service_status == ZX_OK && agent_controller_status != ZX_OK);

    });

    EXPECT_EQ(got_peek_content, expect.got_peek_content);
    EXPECT_EQ(service_status, expect.service_status);
  }
};

TEST_F(AgentServicesTest, ValidAndSuccessfulOneEntry) {
  ConnectToAgentServiceTestConfig test_config;
  test_config.provide_service_name = true;
  test_config.provide_channel = true;
  test_config.provide_agent_controller = true;

  test_config.service_to_agent_map = {
      {kTestServiceName, kTestAgentUrl},
  };

  ConnectToAgentServiceExpect expect;
  expect.got_peek_content = true;

  ExecuteConnectToAgentServiceTest(test_config, expect);
}

TEST_F(AgentServicesTest, ValidAndSuccessfulMultipleEntries) {
  ConnectToAgentServiceTestConfig test_config;
  test_config.provide_service_name = true;
  test_config.provide_channel = true;
  test_config.provide_agent_controller = true;

  test_config.service_to_agent_map = {
      {"chromium.cast.ApplicationConfigManager",
       "fuchsia-pkg:
      {kTestServiceName, kTestAgentUrl},
      {"fuchsia.feedback.DataProvider",
       "fuchsia-pkg:
  };

  ConnectToAgentServiceExpect expect;
  expect.got_peek_content = true;

  ExecuteConnectToAgentServiceTest(test_config, expect);
}

TEST_F(AgentServicesTest, SpecificHandlerProvidedHasService) {
  ConnectToAgentServiceTestConfig test_config;
  test_config.provide_service_name = true;
  test_config.provide_handler = true;
  test_config.provide_channel = true;
  test_config.provide_agent_controller = true;

  test_config.service_to_agent_map = {
      {"chromium.cast.ApplicationConfigManager",
       "fuchsia-pkg:
      {kTestServiceName, kTestAgentUrl},
      {"fuchsia.feedback.DataProvider",
       "fuchsia-pkg:
  };

  ConnectToAgentServiceExpect expect;
  expect.got_peek_content = true;

  ExecuteConnectToAgentServiceTest(test_config, expect);
}

TEST_F(AgentServicesTest, SpecificHandlerProvidedHasServiceButNotInIndex) {
  ConnectToAgentServiceTestConfig test_config;
  test_config.provide_service_name = true;
  test_config.provide_handler = true;
  test_config.provide_channel = true;
  test_config.provide_agent_controller = true;

  test_config.service_to_agent_map = {
      {"chromium.cast.ApplicationConfigManager",
       "fuchsia-pkg:
      {"fuchsia.feedback.DataProvider",
       "fuchsia-pkg:
  };

  ConnectToAgentServiceExpect expect;
  expect.got_peek_content = true;

  ExecuteConnectToAgentServiceTest(test_config, expect);
}

TEST_F(AgentServicesTest, SpecificHandlerProvidedHasServiceButIndexHasDifferentHandler) {
  ConnectToAgentServiceTestConfig test_config;
  test_config.provide_service_name = true;
  test_config.provide_handler = true;
  test_config.provide_channel = true;
  test_config.provide_agent_controller = true;

  test_config.service_to_agent_map = {
      {kTestServiceName, "fuchsia-pkg:
      {"fuchsia.feedback.DataProvider",
       "fuchsia-pkg:
  };

  ConnectToAgentServiceExpect expect;
  expect.got_peek_content = true;

  ExecuteConnectToAgentServiceTest(test_config, expect);
}

TEST_F(AgentServicesTest, NoServiceNameProvided) {
  ConnectToAgentServiceTestConfig test_config;

  test_config.provide_channel = true;
  test_config.provide_agent_controller = true;

  test_config.service_to_agent_map = {
      {"fuchsia.feedback.DataProvider",
       "fuchsia-pkg:
  };

  ConnectToAgentServiceExpect expect;
  expect.service_status = ZX_ERR_PEER_CLOSED;

  ExecuteConnectToAgentServiceTest(test_config, expect);
}

TEST_F(AgentServicesTest, NoChannelProvided) {
  ConnectToAgentServiceTestConfig test_config;
  test_config.provide_service_name = true;

  test_config.provide_agent_controller = true;

  ConnectToAgentServiceExpect expect;
  expect.service_status = ZX_ERR_PEER_CLOSED;

  ExecuteConnectToAgentServiceTest(test_config, expect);
}

TEST_F(AgentServicesTest, NoAgentControllerProvided) {
  ConnectToAgentServiceTestConfig test_config;
  test_config.provide_service_name = true;
  test_config.provide_channel = true;

  ConnectToAgentServiceExpect expect;
  expect.service_status = ZX_ERR_PEER_CLOSED;

  ExecuteConnectToAgentServiceTest(test_config, expect);
}

TEST_F(AgentServicesTest, NoHandlerForService) {
  ConnectToAgentServiceTestConfig test_config;
  test_config.provide_service_name = true;
  test_config.provide_channel = true;
  test_config.provide_agent_controller = true;

  ConnectToAgentServiceExpect expect;
  expect.service_status = ZX_ERR_NOT_FOUND;

  ExecuteConnectToAgentServiceTest(test_config, expect);
}

}
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <fuchsia/modular/testing/cpp/fidl.h>
#include <lib/modular_test_harness/cpp/test_harness_fixture.h>

#include "fuchsia/modular/session/cpp/fidl.h"

namespace {

const std::string kTestAgentUrl(
    "fuchsia-pkg://fuchsia.com/clipboard_agent#meta/clipboard_agent.cmx");
const std::string kTestServiceName(fuchsia::modular::Clipboard::Name_);

// Configuration for testing |ComponentContext| ConnectToAgentService().
struct ConnectToAgentServiceTestConfig {
  // The map of |service_name|->|agent_url| used to look up a service
  // handler |agent_url| by name.
  std::map<std::string, std::string> service_to_agent_map;

  // If true, include the service_name in the |AgentServiceRequest|.
  // This is required for a successful connection.
  bool provide_service_name = false;

  // If true, include the specific handler (agent URL) in the
  // |AgentServiceRequest|. This is *not* required for a successful connection.
  bool provide_handler = false;

  // If true, include the service client-side channel in the
  // |AgentServiceRequest|. This is required for a successful connection.
  bool provide_channel = false;

  // If true, include the |AgentController| in the |AgentServiceRequest|.
  // This is required for a successful connection.
  bool provide_agent_controller = false;

  template <typename Interface>
  fuchsia::modular::AgentServiceRequest MakeAgentServiceRequest(
      std::string service_name, Interface service_request,
      fidl::InterfaceRequest<fuchsia::modular::AgentController> agent_controller) {
    fuchsia::modular::AgentServiceRequest agent_service_request;
    if (provide_service_name) {
      agent_service_request.set_service_name(service_name);
    }
    if (provide_handler) {
      agent_service_request.set_handler(kTestAgentUrl);
    }
    if (provide_channel) {
      agent_service_request.set_channel(service_request.TakeChannel());
    }
    if (provide_agent_controller) {
      agent_service_request.set_agent_controller(std::move(agent_controller));
    }
    return agent_service_request;
  }
};

// Expected test results.
struct ConnectToAgentServiceExpect {
  // If true, the test should connect to the test agent and the requested
  // named service, send a test value, "peek" at the current value, and verify
  // the value is the same.
  bool got_peek_content = false;

  // If set to an error code, the service channel should receive the given
  // error.
  zx_status_t service_status = ZX_OK;
};

class AgentServicesTest : public modular::testing::TestHarnessFixture {
 protected:
  fuchsia::modular::ComponentContextPtr StartTestHarness(
      ConnectToAgentServiceTestConfig test_config) {
    fuchsia::modular::testing::TestHarnessSpec spec;

    std::vector<fuchsia::modular::session::AgentServiceIndexEntry> agent_service_index;

    for (const auto& entry : test_config.service_to_agent_map) {
      fuchsia::modular::session::AgentServiceIndexEntry agent_service;
      agent_service.set_service_name(entry.first);
      agent_service.set_agent_url(entry.second);
      agent_service_index.emplace_back(std::move(agent_service));
    }

    spec.mutable_sessionmgr_config()->set_agent_service_index(std::move(agent_service_index));

    test_harness()->Run(std::move(spec));

    // Create a new story -- this should auto-start the story (because of
    // test_session_shell's behaviour), and launch a new story shell.
    fuchsia::modular::ComponentContextPtr component_context;
    fuchsia::modular::testing::ModularService modular_service;
    modular_service.set_component_context(component_context.NewRequest());
    test_harness()->ConnectToModularService(std::move(modular_service));

    return component_context;
  }

  // Called by test functions to test various input configurations.
  // |test_config| Input configurations and setup options.
  // |expect| Expected results to compare to actual results for the test to
  // confirm.
  void ExecuteConnectToAgentServiceTest(ConnectToAgentServiceTestConfig test_config,
                                        ConnectToAgentServiceExpect expect) {
    const std::string kTestContent("Test clipboard content");

    fuchsia::modular::ComponentContextPtr component_context = StartTestHarness(test_config);

    // Client-side service pointer
    fuchsia::modular::ClipboardPtr clipboard_service_ptr;
    auto service_name = clipboard_service_ptr->Name_;
    auto service_request = clipboard_service_ptr.NewRequest();
    zx_status_t service_status = ZX_OK;
    clipboard_service_ptr.set_error_handler(
        [&service_status](zx_status_t status) { service_status = status; });

    // standard AgentController initialization
    fuchsia::modular::AgentControllerPtr agent_controller;
    zx_status_t agent_controller_status = ZX_OK;
    agent_controller.set_error_handler(
        [&agent_controller_status](zx_status_t status) { agent_controller_status = status; });

    auto agent_service_request = test_config.MakeAgentServiceRequest(
        service_name, std::move(service_request), agent_controller.NewRequest());
    component_context->ConnectToAgentService(std::move(agent_service_request));

    clipboard_service_ptr->Push(kTestContent);
    bool got_peek_content = false;
    clipboard_service_ptr->Peek([&](std::string content) {
      got_peek_content = true;
      EXPECT_EQ(content, kTestContent);
    });

    RunLoopUntil([&] {
      return got_peek_content || service_status != ZX_OK ||
             (expect.service_status == ZX_OK && agent_controller_status != ZX_OK);
      // The order of error callbacks is non-deterministic. If checking for a
      // specific service error, wait for it.
    });

    EXPECT_EQ(got_peek_content, expect.got_peek_content);
    EXPECT_EQ(service_status, expect.service_status);
  }
};

// Ensure Session Manager's ConnectToAgentService can successfully find an
// agent for a given session name, and connect to that agent's service.
TEST_F(AgentServicesTest, ValidAndSuccessfulOneEntry) {
  ConnectToAgentServiceTestConfig test_config;
  test_config.provide_service_name = true;
  test_config.provide_channel = true;
  test_config.provide_agent_controller = true;

  test_config.service_to_agent_map = {
      {kTestServiceName, kTestAgentUrl},
  };

  ConnectToAgentServiceExpect expect;
  expect.got_peek_content = true;

  ExecuteConnectToAgentServiceTest(test_config, expect);
}

// Find agent and service successfully among multiple index entries.
TEST_F(AgentServicesTest, ValidAndSuccessfulMultipleEntries) {
  ConnectToAgentServiceTestConfig test_config;
  test_config.provide_service_name = true;
  test_config.provide_channel = true;
  test_config.provide_agent_controller = true;

  test_config.service_to_agent_map = {
      {"chromium.cast.ApplicationConfigManager",
       "fuchsia-pkg://fuchsia.com/cast_agent#meta/cast_agent.cmx"},
      {kTestServiceName, kTestAgentUrl},
      {"fuchsia.feedback.DataProvider",
       "fuchsia-pkg://fuchsia.com/feedback_agent#meta/feedback_agent.cmx"},
  };

  ConnectToAgentServiceExpect expect;
  expect.got_peek_content = true;

  ExecuteConnectToAgentServiceTest(test_config, expect);
}

// Find service successfully, from a specific handler. The index specifies this
// agent as the default handler, but should not be necessary.
TEST_F(AgentServicesTest, SpecificHandlerProvidedHasService) {
  ConnectToAgentServiceTestConfig test_config;
  test_config.provide_service_name = true;
  test_config.provide_handler = true;
  test_config.provide_channel = true;
  test_config.provide_agent_controller = true;

  test_config.service_to_agent_map = {
      {"chromium.cast.ApplicationConfigManager",
       "fuchsia-pkg://fuchsia.com/cast_agent#meta/cast_agent.cmx"},
      {kTestServiceName, kTestAgentUrl},
      {"fuchsia.feedback.DataProvider",
       "fuchsia-pkg://fuchsia.com/feedback_agent#meta/feedback_agent.cmx"},
  };

  ConnectToAgentServiceExpect expect;
  expect.got_peek_content = true;

  ExecuteConnectToAgentServiceTest(test_config, expect);
}

// Find service successfully, from a specific handler. The index does not
// include the requested service, but it should not be needed since the
// handler is specified.
TEST_F(AgentServicesTest, SpecificHandlerProvidedHasServiceButNotInIndex) {
  ConnectToAgentServiceTestConfig test_config;
  test_config.provide_service_name = true;
  test_config.provide_handler = true;
  test_config.provide_channel = true;
  test_config.provide_agent_controller = true;

  test_config.service_to_agent_map = {
      {"chromium.cast.ApplicationConfigManager",
       "fuchsia-pkg://fuchsia.com/cast_agent#meta/cast_agent.cmx"},
      {"fuchsia.feedback.DataProvider",
       "fuchsia-pkg://fuchsia.com/feedback_agent#meta/feedback_agent.cmx"},
  };

  ConnectToAgentServiceExpect expect;
  expect.got_peek_content = true;

  ExecuteConnectToAgentServiceTest(test_config, expect);
}

// Find service successfully, from a specific handler. The index specifies
// a different agent as the handler, but that agent should not be used since
// a specific agent was specified.
TEST_F(AgentServicesTest, SpecificHandlerProvidedHasServiceButIndexHasDifferentHandler) {
  ConnectToAgentServiceTestConfig test_config;
  test_config.provide_service_name = true;
  test_config.provide_handler = true;
  test_config.provide_channel = true;
  test_config.provide_agent_controller = true;

  test_config.service_to_agent_map = {
      {kTestServiceName, "fuchsia-pkg://fuchsia.com/cast_agent#meta/cast_agent.cmx"},
      {"fuchsia.feedback.DataProvider",
       "fuchsia-pkg://fuchsia.com/feedback_agent#meta/feedback_agent.cmx"},
  };

  ConnectToAgentServiceExpect expect;
  expect.got_peek_content = true;

  ExecuteConnectToAgentServiceTest(test_config, expect);
}

// Bad request
TEST_F(AgentServicesTest, NoServiceNameProvided) {
  ConnectToAgentServiceTestConfig test_config;
  // test_config.provide_service_name = true;
  test_config.provide_channel = true;
  test_config.provide_agent_controller = true;

  test_config.service_to_agent_map = {
      {"fuchsia.feedback.DataProvider",
       "fuchsia-pkg://fuchsia.com/feedback_agent#meta/feedback_agent.cmx"},
  };

  ConnectToAgentServiceExpect expect;
  expect.service_status = ZX_ERR_PEER_CLOSED;

  ExecuteConnectToAgentServiceTest(test_config, expect);
}

// Bad request
TEST_F(AgentServicesTest, NoChannelProvided) {
  ConnectToAgentServiceTestConfig test_config;
  test_config.provide_service_name = true;
  // test_config.provide_channel = true;
  test_config.provide_agent_controller = true;

  ConnectToAgentServiceExpect expect;
  expect.service_status = ZX_ERR_PEER_CLOSED;

  ExecuteConnectToAgentServiceTest(test_config, expect);
}

// Bad request
TEST_F(AgentServicesTest, NoAgentControllerProvided) {
  ConnectToAgentServiceTestConfig test_config;
  test_config.provide_service_name = true;
  test_config.provide_channel = true;
  // test_config.provide_agent_controller = true;

  ConnectToAgentServiceExpect expect;
  expect.service_status = ZX_ERR_PEER_CLOSED;

  ExecuteConnectToAgentServiceTest(test_config, expect);
}

// Attempt to look up the agent based on the service name, but it is not in
// the index.
TEST_F(AgentServicesTest, NoHandlerForService) {
  ConnectToAgentServiceTestConfig test_config;
  test_config.provide_service_name = true;
  test_config.provide_channel = true;
  test_config.provide_agent_controller = true;

  ConnectToAgentServiceExpect expect;
  expect.service_status = ZX_ERR_NOT_FOUND;

  ExecuteConnectToAgentServiceTest(test_config, expect);
}

}  // namespace
