text: // 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 "src/modular/lib/modular_test_harness/cpp/test_harness_impl.h"
  
  #include <fuchsia/modular/session/cpp/fidl.h>
  #include <fuchsia/modular/testing/cpp/fidl.h>
  #include <lib/sys/cpp/testing/test_with_environment_fixture.h>
  #include <lib/vfs/cpp/pseudo_dir.h>
  
  #include <thread>
  
  #include <gtest/gtest.h>
  #include <src/lib/files/file.h>
  #include <src/lib/files/path.h>
  #include <src/modular/lib/modular_config/modular_config.h>
  #include <src/modular/lib/modular_config/modular_config_constants.h>
  #include <src/modular/lib/pseudo_dir/pseudo_dir_server.h>
  
  #include "src/lib/fxl/strings/split_string.h"
  #include "src/lib/fxl/strings/substitute.h"
  
  constexpr char kFakeSessionShellUrl[] =
      "fuchsia-pkg://example.com/FAKE_SESSION_SHELL_PKG/fake_session_shell.cmx";
  constexpr char kFakeStoryShellUrl[] =
      "fuchsia-pkg://example.com/FAKE_STORY_SHELL_PKG/fake_story_shell.cmx";
  constexpr char kFakeModuleUrl[] = "fuchsia-pkg://example.com/FAKE_MODULE_PKG/fake_module.cmx";
  
  namespace modular_testing {
  namespace {
  std::string GenerateFakeUrl() {
    uint32_t random_number = 0;
    zx_cprng_draw(&random_number, sizeof random_number);
    constexpr char kFakeUrlSubstitutePattern[] =
        "fuchsia-pkg://example.com/GENERATED_URL_$0#meta/GENERATED_URL_$0.cmx";
    return fxl::Substitute(kFakeUrlSubstitutePattern, std::to_string(random_number));
  }
  }  // namespace
  
  class TestHarnessImplTest : public gtest::TestWithEnvironmentFixture {
   public:
    TestHarnessImplTest() : harness_impl_(real_env(), [this] { did_exit_ = true; }) {
      harness_impl_.Bind(harness_.NewRequest());
    }
  
    fuchsia::modular::testing::TestHarnessPtr& test_harness() { return harness_; }
  
    bool did_exit() { return did_exit_; }
  
    std::unique_ptr<vfs::PseudoDir> MakeBasemgrConfigDir(
        fuchsia::modular::testing::TestHarnessSpec spec) {
      return TestHarnessImpl::MakeBasemgrConfigDir(std::move(spec));
    }
  
   private:
    bool did_exit_ = false;
    fuchsia::modular::testing::TestHarnessPtr harness_;
    ::modular_testing::TestHarnessImpl harness_impl_;
  };
  
  namespace {
  
  // Check that the config that TestHarnessImpl generates is readable by
  // ModuleConfigReader.
  TEST_F(TestHarnessImplTest, MakeBasemgrConfigDir) {
    constexpr char kSessionShellForTest[] =
        "fuchsia-pkg://example.com/TestHarnessImplTest#meta/"
        "TestHarnessImplTest.cmx";
  
    fuchsia::modular::testing::TestHarnessSpec spec;
    fuchsia::modular::session::SessionShellMapEntry session_shell_entry;
    session_shell_entry.mutable_config()->mutable_app_config()->set_url(kSessionShellForTest);
  
    spec.mutable_basemgr_config()->mutable_session_shell_map()->push_back(
        std::move(session_shell_entry));
  
    // Construct "/config_override/data" dirs, and add MakeBasemgrConfigDir() to
    // "data" dir.
    auto namespace_dir = std::make_unique<vfs::PseudoDir>();
    {
      auto dir_split = fxl::SplitString(modular_config::kOverriddenConfigDir, "/",
                                        fxl::kTrimWhitespace, fxl::kSplitWantNonEmpty);
      ASSERT_EQ(2u, dir_split.size());
  
      auto second_dir = std::make_unique<vfs::PseudoDir>();
      second_dir->AddEntry(std::string(dir_split[1]), MakeBasemgrConfigDir(std::move(spec)));
      namespace_dir->AddEntry(std::string(dir_split[0]), std::move(second_dir));
    }
  
    modular::PseudoDirServer server(std::move(namespace_dir));
    modular::ModularConfigReader config_reader(server.OpenAt("."));
    EXPECT_EQ(kSessionShellForTest,
              config_reader.GetBasemgrConfig().session_shell_map().at(0).config().app_config().url());
  }
  
  // Test that additional injected services are made available, spin up the
  // associated component when requested. This test exercises overriding a default
  // injected service.
  TEST_F(TestHarnessImplTest, DefaultInjectedServices) {
    fuchsia::modular::testing::TestHarnessSpec spec;
  
    auto generated_devicesettingsmgr_url = GenerateFakeUrl();
  
    spec.mutable_env_services()->mutable_services_from_components()->push_back(
        fuchsia::modular::testing::ComponentService{
            // Override the default injected DeviceSettingsManager.
            .name = fuchsia::devicesettings::DeviceSettingsManager::Name_,
            .url = generated_devicesettingsmgr_url});
  
    // Intercept the component URL which supplies DeviceSettingsManager.
    {
      fuchsia::modular::testing::InterceptSpec intercept_spec;
      intercept_spec.set_component_url(generated_devicesettingsmgr_url);
      spec.mutable_components_to_intercept()->push_back(std::move(intercept_spec));
    }
  
    bool intercepted_devicesettingsmgr = false;
    test_harness().events().OnNewComponent =
        [&](fuchsia::sys::StartupInfo startup_info,
            fidl::InterfaceHandle<fuchsia::modular::testing::InterceptedComponent> component) {
          if (startup_info.launch_info.url == generated_devicesettingsmgr_url) {
            intercepted_devicesettingsmgr = true;
          } else {
            ASSERT_FALSE("Started for no reason.");
          }
        };
  
    test_harness()->Run(std::move(spec));
  
    fuchsia::devicesettings::DeviceSettingsManagerPtr devicesettingsmgr;
    test_harness()->ConnectToEnvironmentService(fuchsia::devicesettings::DeviceSettingsManager::Name_,
                                                devicesettingsmgr.NewRequest().TakeChannel());
  
    RunLoopUntil([&] { return intercepted_devicesettingsmgr; });
  }
  
  // Test that additional injected services are made available, spin up the
  // associated component when requested. This test exercises injecting a custom
  // service.
  TEST_F(TestHarnessImplTest, ComponentProvidedService) {
    fuchsia::modular::testing::TestHarnessSpec spec;
  
    auto generated_componentctx_url = GenerateFakeUrl();
  
    spec.mutable_env_services()->mutable_services_from_components()->push_back(
        fuchsia::modular::testing::ComponentService{// Provide a custom injected service.
                                                    .name = fuchsia::modular::ComponentContext::Name_,
                                                    .url = generated_componentctx_url});
  
    // Intercept the component URL which supplies ComponentContext.
    {
      fuchsia::modular::testing::InterceptSpec intercept_spec;
      intercept_spec.set_component_url(generated_componentctx_url);
      spec.mutable_components_to_intercept()->push_back(std::move(intercept_spec));
    }
  
    bool intercepted_componentctx = false;
    test_harness().events().OnNewComponent =
        [&](fuchsia::sys::StartupInfo startup_info,
            fidl::InterfaceHandle<fuchsia::modular::testing::InterceptedComponent> component) {
          if (startup_info.launch_info.url == generated_componentctx_url) {
            intercepted_componentctx = true;
          } else {
            ASSERT_FALSE("Started for no reason.");
          }
        };
  
    test_harness()->Run(std::move(spec));
  
    fuchsia::modular::ComponentContextPtr componentctx;
    test_harness()->ConnectToEnvironmentService(fuchsia::modular::ComponentContext::Name_,
                                                componentctx.NewRequest().TakeChannel());
  
    RunLoopUntil([&] { return intercepted_componentctx; });
  }
  
  TEST_F(TestHarnessImplTest, InterceptSessionShell) {
    fuchsia::modular::testing::TestHarnessSpec spec;
  
    // 1. Setup session shell interception.
    fuchsia::modular::testing::InterceptSpec shell_intercept_spec;
    shell_intercept_spec.set_component_url(kFakeSessionShellUrl);
    {
      fuchsia::modular::session::SessionShellMapEntry entry;
      entry.mutable_config()->mutable_app_config()->set_url(kFakeSessionShellUrl);
  
      spec.mutable_basemgr_config()->mutable_session_shell_map()->push_back(std::move(entry));
    }
    spec.mutable_components_to_intercept()->push_back(std::move(shell_intercept_spec));
  
    // 2. Listen for session shell interception.
    bool intercepted = false;
    test_harness().events().OnNewComponent =
        [&intercepted](
            fuchsia::sys::StartupInfo startup_info,
            fidl::InterfaceHandle<fuchsia::modular::testing::InterceptedComponent> component) {
          if (startup_info.launch_info.url == kFakeSessionShellUrl)
            intercepted = true;
        };
  
    test_harness()->Run(std::move(spec));
  
    RunLoopUntil([&] { return intercepted; });
  }
  
  TEST_F(TestHarnessImplTest, InterceptStoryShellAndModule) {
    // Setup story shell interception.
    fuchsia::modular::testing::InterceptSpec shell_intercept_spec;
    shell_intercept_spec.set_component_url(kFakeStoryShellUrl);
  
    fuchsia::modular::testing::TestHarnessSpec spec;
    spec.mutable_basemgr_config()->mutable_story_shell()->mutable_app_config()->set_url(
        shell_intercept_spec.component_url());
    spec.mutable_components_to_intercept()->push_back(std::move(shell_intercept_spec));
  
    // Setup kFakeModuleUrl interception.
    {
      fuchsia::modular::testing::InterceptSpec intercept_spec;
      intercept_spec.set_component_url(kFakeModuleUrl);
      spec.mutable_components_to_intercept()->push_back(std::move(intercept_spec));
    }
  
    // Listen for story shell interception.
    bool story_shell_intercepted = false;
    // Listen for module interception.
    bool fake_module_intercepted = false;
  
    test_harness().events().OnNewComponent =
        [&](fuchsia::sys::StartupInfo startup_info,
            fidl::InterfaceHandle<fuchsia::modular::testing::InterceptedComponent> component) {
          if (startup_info.launch_info.url == kFakeModuleUrl) {
            fake_module_intercepted = true;
          } else if (startup_info.launch_info.url == kFakeStoryShellUrl) {
            story_shell_intercepted = true;
          }
        };
    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::PuppetMasterPtr puppet_master;
    fuchsia::modular::StoryPuppetMasterPtr story_master;
  
    fuchsia::modular::testing::ModularService svc;
    svc.set_puppet_master(puppet_master.NewRequest());
    test_harness()->ConnectToModularService(std::move(svc));
  
    puppet_master->ControlStory("my_story", story_master.NewRequest());
  
    using fuchsia::modular::AddMod;
    using fuchsia::modular::StoryCommand;
  
    std::vector<StoryCommand> cmds;
    StoryCommand cmd;
    AddMod add_mod;
    add_mod.mod_name = {"mod_name"};
    add_mod.intent.handler = kFakeModuleUrl;
    add_mod.surface_relation = fuchsia::modular::SurfaceRelation{};
    cmd.set_add_mod(std::move(add_mod));
    cmds.push_back(std::move(cmd));
  
    story_master->Enqueue(std::move(cmds));
    story_master->Execute([](fuchsia::modular::ExecuteResult result) {});
  
    RunLoopUntil([&] { return story_shell_intercepted; });
    RunLoopUntil([&] { return fake_module_intercepted; });
  }
  
  // Tests that services in |TestHarnessSpec.env_services.service_dir| are
  // accessible in the test harness environment.
  TEST_F(TestHarnessImplTest, EnvironmentServiceDirectory) {
    constexpr char kTestServiceName[] = "my.test.service";
  
    bool svc_requested = false;
    auto svc_dir = std::make_unique<vfs::PseudoDir>();
    svc_dir->AddEntry(kTestServiceName,
                      std::make_unique<vfs::Service>(
                          [&svc_requested](zx::channel request, async_dispatcher_t* dispatcher) {
                            svc_requested = true;
                          }));
  
    modular::PseudoDirServer svc_dir_server(std::move(svc_dir));
  
    fuchsia::modular::testing::TestHarnessSpec spec;
    spec.mutable_env_services()->set_service_dir(svc_dir_server.Serve().Unbind().TakeChannel());
    test_harness()->Run(std::move(spec));
  
    fuchsia::io::NodePtr node;
    test_harness()->ConnectToEnvironmentService(kTestServiceName, node.NewRequest().TakeChannel());
    RunLoopUntil([&] { return svc_requested; });
  }
  
  // Tests that that the test harness correctly parses modular configs from a
  // string.
  TEST_F(TestHarnessImplTest, ParseConfigFromString) {
    auto config = R"({
    "basemgr": {
      "session_shells": [
        {
          "url": "fuchsia-pkg://fuchsia.com/dev_session_shell#meta/dev_session_shell.cmx"
        }
      ]
    },
    "sessionmgr": {
      "startup_agents": [
        "fuchsia-pkg://fuchsia.com/startup_agent#meta/startup_agent.cmx"
      ]
    }
    })";
  
    fuchsia::modular::session::BasemgrConfig basemgr_config;
    fuchsia::modular::session::SessionmgrConfig sessionmgr_config;
    bool done = false;
    test_harness()->ParseConfig(
        config, [&](fuchsia::modular::session::BasemgrConfig parsed_basemgr_config,
                    fuchsia::modular::session::SessionmgrConfig parsed_sessionmgr_config) {
          basemgr_config = std::move(parsed_basemgr_config);
          sessionmgr_config = std::move(parsed_sessionmgr_config);
          done = true;
        });
  
    RunLoopUntil([&] { return done; });
  
    ASSERT_EQ(1u, basemgr_config.session_shell_map().size());
    EXPECT_EQ("fuchsia-pkg://fuchsia.com/dev_session_shell#meta/dev_session_shell.cmx",
              basemgr_config.session_shell_map().at(0).config().app_config().url());
  }
  
  }  // namespace
  }  // namespace modular_testing
  