[discover] Provide ModuleOutputWriter service to mods

MI4-2404 #done

TESTED=fx run-test modular_integration_tests -- --gtest_filter='ModuleOutputTest.ModuleWritesToOutput'

Change-Id: I738563aa438408f1f7dea1238880b725eb77fd16
diff --git a/peridot/bin/sessionmgr/BUILD.gn b/peridot/bin/sessionmgr/BUILD.gn
index fe98636..6eff7d7 100644
--- a/peridot/bin/sessionmgr/BUILD.gn
+++ b/peridot/bin/sessionmgr/BUILD.gn
@@ -59,6 +59,7 @@
     "//peridot/lib/scoped_tmpfs",
     "//peridot/public/lib/app_driver/cpp:app_driver",
     "//peridot/public/lib/async/cpp:operation",
+    "//sdk/fidl/fuchsia.app.discover",
     "//sdk/fidl/fuchsia.ledger",
     "//sdk/fidl/fuchsia.ledger.cloud",
     "//sdk/fidl/fuchsia.modular",
diff --git a/peridot/bin/sessionmgr/sessionmgr_impl.cc b/peridot/bin/sessionmgr/sessionmgr_impl.cc
index 14af9c2..ed25ca89 100644
--- a/peridot/bin/sessionmgr/sessionmgr_impl.cc
+++ b/peridot/bin/sessionmgr/sessionmgr_impl.cc
@@ -66,6 +66,9 @@
 constexpr char kModuleResolverUrl[] =
     "fuchsia-pkg://fuchsia.com/module_resolver#meta/module_resolver.cmx";
 
+constexpr char kDiscovermgrUrl[] =
+    "fuchsia-pkg://fuchsia.com/discovermgr#meta/discovermgr.cmx";
+
 constexpr char kSessionEnvironmentLabelPrefix[] = "session-";
 
 constexpr char kMessageQueuePath[] = "/data/MESSAGE_QUEUES/v1/";
@@ -213,6 +216,7 @@
 
         InitializeLedger(std::move(ledger_token_manager));
         InitializeMessageQueueManager();
+        InitializeDiscovermgr();
         InitializeMaxwellAndModular(std::move(session_shell_url),
                                     std::move(story_shell_config),
                                     use_session_shell_for_story_shell_factory);
@@ -612,9 +616,9 @@
       session_storage_.get(), std::move(story_shell_config),
       std::move(story_shell_factory_ptr), component_context_info,
       std::move(focus_provider_story_provider),
-      user_intelligence_provider_impl_.get(), module_resolver_service_.get(),
-      entity_provider_runner_.get(), module_facet_reader_.get(),
-      presentation_provider_impl_.get(),
+      user_intelligence_provider_impl_.get(), discover_registry_service_.get(),
+      module_resolver_service_.get(), entity_provider_runner_.get(),
+      module_facet_reader_.get(), presentation_provider_impl_.get(),
       startup_context_
           ->ConnectToEnvironmentService<fuchsia::ui::viewsv1::ViewSnapshot>(),
       (config_.enable_story_shell_preload())));
@@ -631,7 +635,7 @@
       story_provider_puppet_master.NewRequest();
 
   // Initialize the PuppetMaster.
-  // TODO(miguelfrde): there's no clean runtime interface we can inject to
+  // TODO: there's no clean runtime interface we can inject to
   // puppet master. Hence, for now we inject this function to be able to focus
   // mods. Eventually we want to have a StoryRuntime and SessionRuntime classes
   // similar to Story/SessionStorage but for runtime management.
@@ -678,6 +682,41 @@
   AtEnd(Reset(&visible_stories_handler_));
 }
 
+// TODO(MI4-2416): pass additional configuration.
+void SessionmgrImpl::InitializeDiscovermgr() {
+  auto service_list = fuchsia::sys::ServiceList::New();
+  service_list->names.push_back(fuchsia::modular::PuppetMaster::Name_);
+  service_list->names.push_back(fuchsia::modular::EntityResolver::Name_);
+  discovermgr_ns_services_.AddService<fuchsia::modular::PuppetMaster>(
+      [this](auto request) {
+        if (terminating_) {
+          return;
+        }
+        puppet_master_impl_->Connect(std::move(request));
+      });
+  discovermgr_ns_services_.AddService<fuchsia::modular::EntityResolver>(
+      [this](auto request) {
+        if (terminating_) {
+          return;
+        }
+        entity_provider_runner_->ConnectEntityResolver(std::move(request));
+      });
+
+  discovermgr_ns_services_.AddBinding(service_list->provider.NewRequest());
+
+  fuchsia::modular::AppConfig discovermgr_config;
+  discovermgr_config.url = kDiscovermgrUrl;
+
+  discovermgr_app_ = std::make_unique<AppClient<fuchsia::modular::Lifecycle>>(
+      session_environment_->GetLauncher(), std::move(discovermgr_config),
+      "" /* data_origin */, std::move(service_list));
+  discovermgr_app_->services().ConnectToService(
+      discover_registry_service_.NewRequest());
+  AtEnd(Reset(&discover_registry_service_));
+  AtEnd(Reset(&discovermgr_app_));
+  AtEnd(Teardown(kBasicTimeout, "Discovermgr", discovermgr_app_.get()));
+}
+
 void SessionmgrImpl::InitializeSessionShell(
     fuchsia::modular::AppConfig session_shell_config,
     fuchsia::ui::views::ViewToken view_token) {
diff --git a/peridot/bin/sessionmgr/sessionmgr_impl.h b/peridot/bin/sessionmgr/sessionmgr_impl.h
index 21e2cee..b484775 100644
--- a/peridot/bin/sessionmgr/sessionmgr_impl.h
+++ b/peridot/bin/sessionmgr/sessionmgr_impl.h
@@ -5,6 +5,7 @@
 #ifndef PERIDOT_BIN_SESSIONMGR_SESSIONMGR_IMPL_H_
 #define PERIDOT_BIN_SESSIONMGR_SESSIONMGR_IMPL_H_
 
+#include <fuchsia/app/discover/cpp/fidl.h>
 #include <fuchsia/ledger/cloud/cpp/fidl.h>
 #include <fuchsia/ledger/cloud/firestore/cpp/fidl.h>
 #include <fuchsia/ledger/cpp/fidl.h>
@@ -101,6 +102,7 @@
       const fidl::StringPtr& session_shell_url,
       fuchsia::modular::AppConfig story_shell_config,
       bool use_session_shell_for_story_shell_factory);
+  void InitializeDiscovermgr();
   void InitializeSessionShell(fuchsia::modular::AppConfig session_shell_config,
                               fuchsia::ui::views::ViewToken view_token);
 
@@ -224,6 +226,7 @@
   fuchsia::modular::auth::AccountPtr account_;
 
   std::unique_ptr<AppClient<fuchsia::modular::Lifecycle>> context_engine_app_;
+  std::unique_ptr<AppClient<fuchsia::modular::Lifecycle>> discovermgr_app_;
   std::unique_ptr<AppClient<fuchsia::modular::Lifecycle>> module_resolver_app_;
   std::unique_ptr<AppClient<fuchsia::modular::Lifecycle>> session_shell_app_;
   std::unique_ptr<ViewHost> session_shell_view_host_;
@@ -262,6 +265,10 @@
   component::ServiceProviderImpl module_resolver_ns_services_;
   fuchsia::modular::ModuleResolverPtr module_resolver_service_;
 
+  // Services we provide to the discovermgr's namespace.
+  component::ServiceProviderImpl discovermgr_ns_services_;
+  fuchsia::app::discover::DiscoverRegistryPtr discover_registry_service_;
+
   class PresentationProviderImpl;
   std::unique_ptr<PresentationProviderImpl> presentation_provider_impl_;
 
diff --git a/peridot/bin/sessionmgr/story_runner/BUILD.gn b/peridot/bin/sessionmgr/story_runner/BUILD.gn
index a0a798e..c703444 100644
--- a/peridot/bin/sessionmgr/story_runner/BUILD.gn
+++ b/peridot/bin/sessionmgr/story_runner/BUILD.gn
@@ -69,6 +69,7 @@
     "//peridot/public/lib/context/cpp:context_helper",
     "//peridot/public/lib/entity/cpp:json",
     "//peridot/public/lib/fostr/fidl/fuchsia.modular",
+    "//sdk/fidl/fuchsia.app.discover",
     "//sdk/fidl/fuchsia.ledger",
     "//sdk/fidl/fuchsia.modular",
     "//sdk/fidl/fuchsia.modular.internal",
diff --git a/peridot/bin/sessionmgr/story_runner/module_context_impl.cc b/peridot/bin/sessionmgr/story_runner/module_context_impl.cc
index aab49c2..ea7256b 100644
--- a/peridot/bin/sessionmgr/story_runner/module_context_impl.cc
+++ b/peridot/bin/sessionmgr/story_runner/module_context_impl.cc
@@ -29,7 +29,8 @@
                                   info.story_controller_impl->GetStoryId()),
                               EncodeModulePath(module_data_->module_path),
                               module_data_->module_url),
-      user_intelligence_provider_(info.user_intelligence_provider) {
+      user_intelligence_provider_(info.user_intelligence_provider),
+      discover_registry_(info.discover_registry) {
   service_provider_impl_.AddService<fuchsia::modular::ComponentContext>(
       [this](
           fidl::InterfaceRequest<fuchsia::modular::ComponentContext> request) {
@@ -52,6 +53,14 @@
         user_intelligence_provider_->GetComponentIntelligenceServices(
             std::move(*scope), std::move(request));
       });
+  service_provider_impl_.AddService<fuchsia::app::discover::ModuleOutputWriter>(
+      [this](auto request) {
+        fuchsia::app::discover::ModuleIdentifier module_scope;
+        module_scope.set_story_id(story_controller_impl_->GetStoryId());
+        module_scope.set_module_path(module_data_->module_path);
+        discover_registry_->RegisterModuleOutputWriter(std::move(module_scope),
+                                                       std::move(request));
+      });
   service_provider_impl_.AddBinding(std::move(service_provider_request));
 }
 
diff --git a/peridot/bin/sessionmgr/story_runner/module_context_impl.h b/peridot/bin/sessionmgr/story_runner/module_context_impl.h
index 00f2f9a..0abf2e6 100644
--- a/peridot/bin/sessionmgr/story_runner/module_context_impl.h
+++ b/peridot/bin/sessionmgr/story_runner/module_context_impl.h
@@ -5,6 +5,7 @@
 #ifndef PERIDOT_BIN_SESSIONMGR_STORY_RUNNER_MODULE_CONTEXT_IMPL_H_
 #define PERIDOT_BIN_SESSIONMGR_STORY_RUNNER_MODULE_CONTEXT_IMPL_H_
 
+#include <fuchsia/app/discover/cpp/fidl.h>
 #include <fuchsia/modular/cpp/fidl.h>
 #include <fuchsia/ui/views/cpp/fidl.h>
 #include <lib/component/cpp/service_provider_impl.h>
@@ -30,6 +31,7 @@
   StoryControllerImpl* const story_controller_impl;
   StoryVisibilitySystem* const story_visibility_system;
   fuchsia::modular::UserIntelligenceProvider* const user_intelligence_provider;
+  fuchsia::app::discover::DiscoverRegistry* const discover_registry;
 };
 
 // ModuleContextImpl keeps a single connection from a module instance in
@@ -131,6 +133,9 @@
   fuchsia::modular::UserIntelligenceProvider* const
       user_intelligence_provider_;  // Not owned
 
+  fuchsia::app::discover::DiscoverRegistry* const
+      discover_registry_;  // Not owned
+
   fidl::BindingSet<fuchsia::modular::ModuleContext> bindings_;
 
   // A service provider that represents the services to be added into an
diff --git a/peridot/bin/sessionmgr/story_runner/story_controller_impl.cc b/peridot/bin/sessionmgr/story_runner/story_controller_impl.cc
index b9fdbae..5c39d37 100644
--- a/peridot/bin/sessionmgr/story_runner/story_controller_impl.cc
+++ b/peridot/bin/sessionmgr/story_runner/story_controller_impl.cc
@@ -4,6 +4,7 @@
 
 #include "peridot/bin/sessionmgr/story_runner/story_controller_impl.h"
 
+#include <fuchsia/app/discover/cpp/fidl.h>
 #include <fuchsia/ledger/cpp/fidl.h>
 #include <fuchsia/modular/cpp/fidl.h>
 #include <fuchsia/modular/internal/cpp/fidl.h>
@@ -194,6 +195,8 @@
     service_list->names.push_back(fuchsia::modular::ModuleContext::Name_);
     service_list->names.push_back(
         fuchsia::modular::IntelligenceServices::Name_);
+    service_list->names.push_back(
+        fuchsia::app::discover::ModuleOutputWriter::Name_);
     service_list->provider = std::move(module_context_provider);
 
     RunningModInfo running_mod_info;
@@ -235,7 +238,9 @@
         story_controller_impl_,
         story_controller_impl_->story_visibility_system_,
         story_controller_impl_->story_provider_impl_
-            ->user_intelligence_provider()};
+            ->user_intelligence_provider(),
+        story_controller_impl_->story_provider_impl_->discover_registry(),
+    };
 
     running_mod_info.module_context_impl = std::make_unique<ModuleContextImpl>(
         module_context_info, running_mod_info.module_data.get(),
diff --git a/peridot/bin/sessionmgr/story_runner/story_provider_impl.cc b/peridot/bin/sessionmgr/story_runner/story_provider_impl.cc
index 709bd7f..4207e35 100644
--- a/peridot/bin/sessionmgr/story_runner/story_provider_impl.cc
+++ b/peridot/bin/sessionmgr/story_runner/story_provider_impl.cc
@@ -298,6 +298,7 @@
     fuchsia::modular::FocusProviderPtr focus_provider,
     fuchsia::modular::UserIntelligenceProvider* const
         user_intelligence_provider,
+    fuchsia::app::discover::DiscoverRegistry* const discover_registry,
     fuchsia::modular::ModuleResolver* const module_resolver,
     EntityProviderRunner* const entity_provider_runner,
     modular::ModuleFacetReader* const module_facet_reader,
@@ -312,6 +313,7 @@
       enable_story_shell_preload_(enable_story_shell_preload),
       component_context_info_(component_context_info),
       user_intelligence_provider_(user_intelligence_provider),
+      discover_registry_(discover_registry),
       module_resolver_(module_resolver),
       entity_provider_runner_(entity_provider_runner),
       module_facet_reader_(module_facet_reader),
@@ -695,8 +697,7 @@
     auto done = on_run->AsyncMap([this, story_id = info->focused_story_id] {
       zx_time_t now = 0;
       zx_clock_get_new(ZX_CLOCK_UTC, &now);
-      return session_storage_->UpdateLastFocusedTimestamp(
-          story_id, now);
+      return session_storage_->UpdateLastFocusedTimestamp(story_id, now);
     });
     fit::function<void()> callback = [] {};
     operation_queue_.Add(WrapFutureAsOperation(
diff --git a/peridot/bin/sessionmgr/story_runner/story_provider_impl.h b/peridot/bin/sessionmgr/story_runner/story_provider_impl.h
index 45fd7b1..87d2cf2 100644
--- a/peridot/bin/sessionmgr/story_runner/story_provider_impl.h
+++ b/peridot/bin/sessionmgr/story_runner/story_provider_impl.h
@@ -5,6 +5,7 @@
 #ifndef PERIDOT_BIN_SESSIONMGR_STORY_RUNNER_STORY_PROVIDER_IMPL_H_
 #define PERIDOT_BIN_SESSIONMGR_STORY_RUNNER_STORY_PROVIDER_IMPL_H_
 
+#include <fuchsia/app/discover/cpp/fidl.h>
 #include <fuchsia/ledger/cpp/fidl.h>
 #include <fuchsia/modular/cpp/fidl.h>
 #include <fuchsia/modular/internal/cpp/fidl.h>
@@ -59,6 +60,7 @@
       const ComponentContextInfo& component_context_info,
       fuchsia::modular::FocusProviderPtr focus_provider,
       fuchsia::modular::UserIntelligenceProvider* user_intelligence_provider,
+      fuchsia::app::discover::DiscoverRegistry* discover_registry,
       fuchsia::modular::ModuleResolver* module_resolver,
       EntityProviderRunner* entity_provider_runner,
       modular::ModuleFacetReader* module_facet_reader,
@@ -97,6 +99,11 @@
   }
 
   // Called by StoryControllerImpl.
+  fuchsia::app::discover::DiscoverRegistry* discover_registry() {
+    return discover_registry_;
+  }
+
+  // Called by StoryControllerImpl.
   fuchsia::modular::ModuleResolver* module_resolver() {
     return module_resolver_;
   }
@@ -310,7 +317,9 @@
   const ComponentContextInfo component_context_info_;
 
   fuchsia::modular::UserIntelligenceProvider* const
-      user_intelligence_provider_;                           // Not owned.
+      user_intelligence_provider_;  // Not owned.
+  fuchsia::app::discover::DiscoverRegistry* const
+      discover_registry_;                                    // Not owned.
   fuchsia::modular::ModuleResolver* const module_resolver_;  // Not owned.
   EntityProviderRunner* const entity_provider_runner_;       // Not owned.
   modular::ModuleFacetReader* const module_facet_reader_;    // Not owned.
diff --git a/peridot/tests/modular_tests.json b/peridot/tests/modular_tests.json
index d210eaa0..e4ce802 100644
--- a/peridot/tests/modular_tests.json
+++ b/peridot/tests/modular_tests.json
@@ -128,4 +128,4 @@
       ]
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/sdk/fidl/fuchsia.modular/entity/entity_resolver.fidl b/sdk/fidl/fuchsia.modular/entity/entity_resolver.fidl
index 2cbb35a..bcea5520 100644
--- a/sdk/fidl/fuchsia.modular/entity/entity_resolver.fidl
+++ b/sdk/fidl/fuchsia.modular/entity/entity_resolver.fidl
@@ -6,6 +6,7 @@
 
 // This interface is given to all components in their |ComponentContext|. Any
 // component can resolve an entity reference into an |Entity|.
+[Discoverable]
 protocol EntityResolver {
     // Finds and binds |entity_request| to an Entity handle for the Entity
     // referenced by |entity_reference|. If an error occurs, |entity_request|
diff --git a/sdk/fidl/fuchsia.modular/fuchsia.modular.api b/sdk/fidl/fuchsia.modular/fuchsia.modular.api
index 4a3e03c..c29c790 100644
--- a/sdk/fidl/fuchsia.modular/fuchsia.modular.api
+++ b/sdk/fidl/fuchsia.modular/fuchsia.modular.api
@@ -19,7 +19,7 @@
   "fidl/fuchsia.modular/entity/entity.fidl": "ca4659a784d2fd345a5348bcc0c78042",
   "fidl/fuchsia.modular/entity/entity_provider.fidl": "8e3026210f5ad83d3c357603094877f0",
   "fidl/fuchsia.modular/entity/entity_reference_factory.fidl": "5fc60e7fb24bd7878bd6203dc64bc915",
-  "fidl/fuchsia.modular/entity/entity_resolver.fidl": "ab460a5e575d789311e63a54085c5930",
+  "fidl/fuchsia.modular/entity/entity_resolver.fidl": "9c1094116d0ef67d16d720069e41071b",
   "fidl/fuchsia.modular/intent/intent.fidl": "de3880a06d64fa18dbbedcee7ac08053",
   "fidl/fuchsia.modular/intent/intent_handler.fidl": "476ec09d958d7ba9b4934d9e4c255c90",
   "fidl/fuchsia.modular/lifecycle/lifecycle.fidl": "1bc186061e6c737b060113bd203386b4",
diff --git a/src/modular/tests/BUILD.gn b/src/modular/tests/BUILD.gn
index 9ebb6f8..e6a5db9 100644
--- a/src/modular/tests/BUILD.gn
+++ b/src/modular/tests/BUILD.gn
@@ -78,6 +78,22 @@
   ]
 }
 
+executable("module_output_test") {
+  testonly = true
+
+  sources = [
+    "module_output_test.cc",
+  ]
+
+  deps = [
+    "//peridot/public/lib/modular_test_harness/cpp:test_harness_fixture",
+    "//sdk/fidl/fuchsia.app.discover",
+    "//sdk/fidl/fuchsia.modular.testing",
+    "//sdk/lib/sys/cpp/testing:integration",
+    "//third_party/googletest:gtest_main",
+  ]
+}
+
 executable("story_shell_factory_test") {
   testonly = true
 
@@ -117,6 +133,10 @@
       environments = basic_envs
     },
     {
+      name = "module_output_test"
+      environments = basic_envs
+    },
+    {
       name = "story_shell_factory_test"
       environments = basic_envs
     },
@@ -125,6 +145,7 @@
   deps = [
     ":intents_test",
     ":last_focus_time_test",
+    ":module_output_test",
     ":session_shell_test",
     ":story_shell_factory_test",
     "//garnet/public/lib/callback",
diff --git a/src/modular/tests/meta/module_output_test.cmx b/src/modular/tests/meta/module_output_test.cmx
new file mode 100644
index 0000000..433f1ad
--- /dev/null
+++ b/src/modular/tests/meta/module_output_test.cmx
@@ -0,0 +1,13 @@
+{
+    "program": {
+        "binary": "test/module_output_test"
+    },
+    "sandbox": {
+        "services": [
+            "fuchsia.app.discover.ModuleOutput",
+            "fuchsia.sys.Launcher",
+            "fuchsia.sys.Environment",
+            "fuchsia.modular.ModuleContext"
+        ]
+    }
+}
diff --git a/src/modular/tests/module_output_test.cc b/src/modular/tests/module_output_test.cc
new file mode 100644
index 0000000..1491cee
--- /dev/null
+++ b/src/modular/tests/module_output_test.cc
@@ -0,0 +1,94 @@
+// 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/app/discover/cpp/fidl.h>
+#include <fuchsia/modular/cpp/fidl.h>
+#include <fuchsia/modular/testing/cpp/fidl.h>
+#include <lib/modular_test_harness/cpp/fake_component.h>
+#include <lib/modular_test_harness/cpp/test_harness_fixture.h>
+#include <sdk/lib/sys/cpp/testing/test_with_environment.h>
+
+namespace {
+
+constexpr char kModuleName[] = "mod_name";
+constexpr char kStoryName[] = "story";
+constexpr char kIntentAction[] = "action";
+
+constexpr zx::duration kTimeout = zx::sec(15);
+
+class ModuleOutputTest : public modular::testing::TestHarnessFixture {
+ public:
+  void SetUp() override {
+    test_module_url_ = builder_.GenerateFakeUrl();
+    builder_.InterceptComponent(
+        component_.GetOnCreateHandler(),
+        {.url = test_module_url_,
+         .sandbox_services = {"fuchsia.app.discover.ModuleOutputWriter",
+                              "fuchsia.modular.ModuleContext"}});
+
+    test_harness().events().OnNewComponent =
+        builder_.BuildOnNewComponentHandler();
+    test_harness()->Run(builder_.BuildSpec());
+  }
+
+ protected:
+  void CreateStoryAndAddMod(fuchsia::modular::Intent intent) {
+    // Create an AddMod command
+    fuchsia::modular::AddMod add_mod;
+    add_mod.mod_name = {kModuleName};
+    add_mod.intent = std::move(intent);
+    add_mod.surface_relation = fuchsia::modular::SurfaceRelation{};
+
+    fuchsia::modular::StoryCommand cmd;
+    cmd.set_add_mod(std::move(add_mod));
+
+    std::vector<fuchsia::modular::StoryCommand> cmds;
+    cmds.push_back(std::move(cmd));
+
+    // Connect to PuppetMaster Service
+    fuchsia::modular::PuppetMasterPtr puppet_master;
+    fuchsia::modular::testing::ModularService svc;
+    svc.set_puppet_master(puppet_master.NewRequest());
+    test_harness()->ConnectToModularService(std::move(svc));
+
+    // Create a story
+    fuchsia::modular::StoryPuppetMasterPtr story_master;
+    puppet_master->ControlStory(kStoryName, story_master.NewRequest());
+
+    // Add the initial module to the story
+    story_master->Enqueue(std::move(cmds));
+    story_master->Execute([&](fuchsia::modular::ExecuteResult result) {});
+  }
+
+  modular::testing::FakeComponent component_;
+  modular::testing::TestHarnessBuilder builder_;
+  std::string test_module_url_;
+};
+
+TEST_F(ModuleOutputTest, ModuleWritesToOutput) {
+  fuchsia::modular::Intent intent;
+  intent.handler = test_module_url_;
+  intent.action = kIntentAction;
+
+  CreateStoryAndAddMod(std::move(intent));
+  ASSERT_TRUE(RunLoopWithTimeoutOrUntil([&] { return component_.is_running(); },
+                                        kTimeout));
+
+  fuchsia::app::discover::ModuleOutputWriterPtr module_output;
+  component_.component_context()->svc()->Connect(module_output.NewRequest());
+  bool output_written{false};
+  module_output->Write("output_name", "reference",
+                       [&output_written](auto result) {
+                         // TODO: once the discover service generates
+                         // suggestions, we should ensure they are generated
+                         // based on this modules output.
+                         ASSERT_TRUE(result.is_response());
+                         output_written = true;
+                       });
+
+  ASSERT_TRUE(RunLoopWithTimeoutOrUntil(
+      [&output_written] { return output_written; }, kTimeout));
+}
+
+}  // namespace