blob: dc0484b5cbbe48b93e84abdc7d8f4c6a548122a8 [file] [log] [blame]
// Copyright 2016 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/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/context/cpp/context_helper.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/fidl/cpp/optional.h>
#include <src/lib/fxl/logging.h>
#include <lib/svc/cpp/services.h>
#include "peridot/lib/rapidjson/rapidjson.h"
#include "peridot/lib/testing/story_provider_mock.h"
#include "peridot/lib/testing/wait_until_idle.h"
#include "peridot/tests/maxwell_integration/context_engine_test_base.h"
#include "peridot/tests/maxwell_integration/test_suggestion_listener.h"
#include "rapidjson/document.h"
#include "rapidjson/pointer.h"
namespace maxwell {
namespace {
// context agent that publishes an int n
class NWriter {
public:
NWriter(fuchsia::modular::ContextEngine* context_engine) {
fuchsia::modular::ComponentScope scope;
scope.set_global_scope(fuchsia::modular::GlobalScope());
context_engine->GetWriter(std::move(scope), pub_.NewRequest());
}
void Publish(int n) { pub_->WriteEntityTopic("n", std::to_string(n)); }
private:
fuchsia::modular::ContextWriterPtr pub_;
};
fuchsia::modular::Proposal CreateProposal(
const std::string& id, const std::string& headline,
std::vector<fuchsia::modular::StoryCommand> commands,
fuchsia::modular::AnnoyanceType annoyance) {
fuchsia::modular::Proposal p;
p.id = id;
p.on_selected = std::move(commands);
p.affinity.resize(0);
fuchsia::modular::SuggestionDisplay d;
d.headline = headline;
d.color = 0x00aa00aa; // argb purple
d.annoyance = annoyance;
p.display = std::move(d);
return p;
}
class Proposinator {
public:
Proposinator(fuchsia::modular::SuggestionEngine* suggestion_engine,
fidl::StringPtr url = "Proposinator") {
suggestion_engine->RegisterProposalPublisher("Proposinator",
out_.NewRequest());
}
virtual ~Proposinator() = default;
void Propose(const std::string& id,
std::vector<fuchsia::modular::StoryCommand> commands = {}) {
Propose(id, id, fuchsia::modular::AnnoyanceType::NONE, std::move(commands));
}
void Propose(const std::string& id, const std::string& headline,
fuchsia::modular::AnnoyanceType annoyance =
fuchsia::modular::AnnoyanceType::NONE,
std::vector<fuchsia::modular::StoryCommand> commands = {}) {
Propose(CreateProposal(id, headline, std::move(commands), annoyance));
}
void Propose(fuchsia::modular::Proposal proposal) {
out_->Propose(std::move(proposal));
}
void Remove(const std::string& id) { out_->Remove(id); }
void KillPublisher() { out_.Unbind(); }
protected:
fuchsia::modular::ProposalPublisherPtr out_;
};
class AskProposinator : public Proposinator,
public fuchsia::modular::QueryHandler {
public:
AskProposinator(fuchsia::modular::SuggestionEngine* suggestion_engine,
async::Loop* loop, fidl::StringPtr url = "AskProposinator")
: Proposinator(suggestion_engine, url), loop_(loop), ask_binding_(this) {
fidl::InterfaceHandle<fuchsia::modular::QueryHandler> query_handle;
ask_binding_.Bind(query_handle.NewRequest());
suggestion_engine->RegisterQueryHandler(url, std::move(query_handle));
}
void OnQuery(fuchsia::modular::UserInput query,
OnQueryCallback callback) override {
query_ = fidl::MakeOptional(query);
query_callback_ = std::move(callback);
query_proposals_.resize(0);
if (waiting_for_query_) {
waiting_for_query_ = false;
async::PostTask(loop_->dispatcher(), [this] { loop_->Quit(); });
}
}
void WaitForQuery() {
waiting_for_query_ = true;
loop_->Run();
loop_->ResetQuit();
}
void Commit() {
fuchsia::modular::QueryResponse response;
response.proposals = std::move(query_proposals_);
query_callback_(std::move(response));
}
fidl::StringPtr query() const { return query_ ? query_->text : nullptr; }
void ProposeForAsk(const std::string& id) {
std::vector<fuchsia::modular::StoryCommand> commands;
ProposeForAsk(id, id, fuchsia::modular::AnnoyanceType::NONE,
std::move(commands));
}
void ProposeForAsk(
const std::string& id, const std::string& headline,
fuchsia::modular::AnnoyanceType annoyance =
fuchsia::modular::AnnoyanceType::NONE,
std::vector<fuchsia::modular::StoryCommand> commands = {}) {
query_proposals_.push_back(
CreateProposal(id, headline, std::move(commands), annoyance));
}
private:
async::Loop* const loop_;
fidl::Binding<fuchsia::modular::QueryHandler> ask_binding_;
fuchsia::modular::UserInputPtr query_;
std::vector<fuchsia::modular::Proposal> query_proposals_;
OnQueryCallback query_callback_;
bool waiting_for_query_ = false;
};
// maintains the number of proposals specified by the context field "n"
class NProposals : public Proposinator,
public fuchsia::modular::ContextListener {
public:
NProposals(fuchsia::modular::ContextEngine* context_engine,
fuchsia::modular::SuggestionEngine* suggestion_engine)
: Proposinator(suggestion_engine, "NProposals"), listener_binding_(this) {
fuchsia::modular::ComponentScope scope;
scope.set_global_scope(fuchsia::modular::GlobalScope());
context_engine->GetReader(std::move(scope), reader_.NewRequest());
fuchsia::modular::ContextSelector selector;
selector.type = fuchsia::modular::ContextValueType::ENTITY;
selector.meta = fuchsia::modular::ContextMetadata::New();
selector.meta->entity = fuchsia::modular::EntityMetadata::New();
selector.meta->entity->topic = "n";
fuchsia::modular::ContextQuery query;
modular::AddToContextQuery(&query, "n", std::move(selector));
reader_->Subscribe(std::move(query), listener_binding_.NewBinding());
}
void OnContextUpdate(fuchsia::modular::ContextUpdate update) override {
auto maybe_result = modular::TakeContextValue(&update, "n");
if (!maybe_result) {
FXL_LOG(INFO) << "Expect an update key for every query key.";
}
auto& r = maybe_result.value();
if (r.empty()) {
return;
}
int n = std::stoi(r.at(0).content);
for (int i = n_; i < n; i++) {
Propose(std::to_string(i));
}
for (int i = n; i < n_; i++) {
Remove(std::to_string(i));
}
n_ = n;
}
private:
fuchsia::modular::ContextReaderPtr reader_;
fidl::Binding<fuchsia::modular::ContextListener> listener_binding_;
int n_ = 0;
};
class SuggestionEngineTest : public ContextEngineTestBase,
fuchsia::modular::ProposalListener {
public:
SuggestionEngineTest() : story_provider_binding_(&story_provider_) {}
void SetUp() override {
ContextEngineTestBase::SetUp();
component::Services suggestion_services = StartServices(
"fuchsia-pkg://fuchsia.com/suggestion_engine#meta/"
"suggestion_engine.cmx");
suggestion_engine_ =
suggestion_services
.ConnectToService<fuchsia::modular::SuggestionEngine>();
suggestion_provider_ =
suggestion_services
.ConnectToService<fuchsia::modular::SuggestionProvider>();
suggestion_debug_ =
suggestion_services
.ConnectToService<fuchsia::modular::SuggestionDebug>();
}
protected:
fuchsia::modular::SuggestionEngine* suggestion_engine() {
return suggestion_engine_.get();
}
fuchsia::modular::SuggestionProvider* suggestion_provider() {
return suggestion_provider_.get();
}
fuchsia::modular::SuggestionDebug* suggestion_debug() {
return suggestion_debug_.get();
}
modular::StoryProviderMock* story_provider() { return &story_provider_; }
void StartSuggestionAgent(const std::string& url) {
auto agent_bridge =
std::make_unique<MaxwellServiceProviderBridge>(root_environment());
agent_bridge->AddService<fuchsia::modular::ContextReader>(
[this,
url](fidl::InterfaceRequest<fuchsia::modular::ContextReader> request) {
fuchsia::modular::ComponentScope scope;
fuchsia::modular::AgentScope agent_scope;
agent_scope.url = url;
scope.set_agent_scope(std::move(agent_scope));
context_engine()->GetReader(std::move(scope), std::move(request));
});
agent_bridge->AddService<fuchsia::modular::ProposalPublisher>(
[this, url](fidl::InterfaceRequest<fuchsia::modular::ProposalPublisher>
request) {
suggestion_engine_->RegisterProposalPublisher(url,
std::move(request));
});
StartAgent(url, std::move(agent_bridge));
}
void AcceptSuggestion(const std::string& suggestion_id) {
Interact(suggestion_id, fuchsia::modular::InteractionType::SELECTED);
}
void DismissSuggestion(const std::string& suggestion_id) {
Interact(suggestion_id, fuchsia::modular::InteractionType::DISMISSED);
}
void WaitUntilIdle() {
ContextEngineTestBase::WaitUntilIdle();
util::WaitUntilIdle(&suggestion_debug_, &loop_);
}
void RunUntilIdle() {
suggestion_debug()->RunUntilIdle([this] { loop_.Quit(); });
loop_.Run();
loop_.ResetQuit();
loop_.RunUntilIdle();
}
void AddProposalListenerBinding(
fidl::InterfaceRequest<fuchsia::modular::ProposalListener> request) {
proposal_listener_bindings_.AddBinding(this, std::move(request));
}
// The id of the most recently accepted proposal.
std::string accepted_proposal_id_;
// The amount of proposals that have been accepted, as indicated by calls to
// |OnProposalAccepted|.
int accepted_proposal_count_ = 0;
// Whether or not a successful create story action has been observed.
bool created_story_action_;
// Story ID when a proposal is accepted. This is the ID of a story that was
// created as result of the execution of a proposal actions, either through
// AddModule or for a rich suggestion.
std::string accepted_proposal_story_id_;
private:
void Interact(const std::string& suggestion_id,
fuchsia::modular::InteractionType interaction_type) {
fuchsia::modular::Interaction interaction;
interaction.type = interaction_type;
suggestion_provider_->NotifyInteraction(suggestion_id,
std::move(interaction));
}
// |fuchsia::modular::ProposalListener|
void OnProposalAccepted(std::string proposal_id,
fidl::StringPtr story_id) override {
accepted_proposal_id_ = proposal_id;
if (!story_id->empty()) {
created_story_action_ = true;
accepted_proposal_story_id_ = story_id;
}
accepted_proposal_count_++;
}
fuchsia::modular::SuggestionEnginePtr suggestion_engine_;
fuchsia::modular::SuggestionDebugPtr suggestion_debug_;
fuchsia::modular::SuggestionProviderPtr suggestion_provider_;
modular::StoryProviderMock story_provider_;
fidl::Binding<fuchsia::modular::StoryProvider> story_provider_binding_;
fidl::BindingSet<fuchsia::modular::ProposalListener>
proposal_listener_bindings_;
};
class AskTest : public virtual SuggestionEngineTest {
public:
AskTest()
: listener_binding_(&listener_),
debug_listener_binding_(&debug_listener_) {}
void SetUp() override {
SuggestionEngineTest::SetUp();
suggestion_debug()->WatchAskProposals(debug_listener_binding_.NewBinding());
}
void CloseAndResetListener() {
if (listener_binding_.is_bound()) {
listener_binding_.Unbind();
listener_.ClearSuggestions();
}
}
void Query(const std::string& query, int count = 10) {
CloseAndResetListener();
fuchsia::modular::UserInput input;
input.type = fuchsia::modular::InputType::TEXT;
input.text = query;
suggestion_provider()->Query(listener_binding_.NewBinding(),
std::move(input), count);
}
int suggestion_count() const { return listener_.suggestion_count(); }
modular::TestSuggestionListener* listener() { return &listener_; }
protected:
void EnsureDebugMatches() {
auto& subscriberAsks = listener_.GetSuggestions();
auto& debugAsks = debug_listener_.GetProposals();
EXPECT_GE(debugAsks.size(), subscriberAsks.size());
for (size_t i = 0; i < subscriberAsks.size(); i++) {
auto& suggestion = subscriberAsks[i];
auto& proposal = debugAsks[i];
EXPECT_EQ(suggestion->display.headline, proposal.display.headline);
EXPECT_EQ(suggestion->display.subheadline, proposal.display.subheadline);
EXPECT_EQ(suggestion->display.details, proposal.display.details);
}
}
private:
modular::TestSuggestionListener listener_;
modular::TestDebugAskListener debug_listener_;
fidl::Binding<fuchsia::modular::QueryListener> listener_binding_;
fidl::Binding<fuchsia::modular::AskProposalListener> debug_listener_binding_;
};
class InterruptionTest : public virtual SuggestionEngineTest {
public:
InterruptionTest()
: listener_binding_(&listener_),
debug_listener_binding_(&debug_listener_) {}
void SetUp() override {
SuggestionEngineTest::SetUp();
suggestion_provider()->SubscribeToInterruptions(
listener_binding_.NewBinding());
suggestion_debug()->WatchInterruptionProposals(
debug_listener_binding_.NewBinding());
// Make sure we're subscribed before we start the test.
WaitUntilIdle();
}
modular::TestDebugInterruptionListener* debugListener() {
return &debug_listener_;
}
modular::TestSuggestionListener* listener() { return &listener_; }
protected:
int suggestion_count() const { return listener_.suggestion_count(); }
void EnsureDebugMatches() {
auto& subscriberNexts = listener_.GetSuggestions();
auto lastInterruption = debug_listener_.get_interrupt_proposal();
ASSERT_GE(subscriberNexts.size(), 1u);
auto& suggestion = subscriberNexts[0];
EXPECT_EQ(suggestion->display.headline, lastInterruption.display.headline);
EXPECT_EQ(suggestion->display.subheadline,
lastInterruption.display.subheadline);
EXPECT_EQ(suggestion->display.details, lastInterruption.display.details);
}
private:
modular::TestSuggestionListener listener_;
modular::TestDebugInterruptionListener debug_listener_;
fidl::Binding<fuchsia::modular::InterruptionListener> listener_binding_;
fidl::Binding<fuchsia::modular::InterruptionProposalListener>
debug_listener_binding_;
};
class NextTest : public virtual SuggestionEngineTest {
public:
NextTest()
: listener_binding_(&listener_),
debug_listener_binding_(&debug_listener_) {}
void SetUp() override {
SuggestionEngineTest::SetUp();
suggestion_debug()->WatchNextProposals(
debug_listener_binding_.NewBinding());
}
modular::TestDebugNextListener* debugListener() { return &debug_listener_; }
modular::TestSuggestionListener* listener() { return &listener_; }
protected:
void StartListening(int count) {
suggestion_provider()->SubscribeToNext(listener_binding_.NewBinding(),
count);
}
void CloseAndResetListener() {
listener_binding_.Unbind();
listener_.ClearSuggestions();
}
void SetResultCount(int count) {
CloseAndResetListener();
suggestion_provider()->SubscribeToNext(listener_binding_.NewBinding(),
count);
}
int suggestion_count() const { return listener_.suggestion_count(); }
const fuchsia::modular::Suggestion* GetOnlySuggestion() const {
return listener_.GetOnlySuggestion();
}
void EnsureDebugMatches() {
auto& subscriberNexts = listener_.GetSuggestions();
auto& debugNexts = debug_listener_.GetProposals();
EXPECT_GE(debugNexts.size(), subscriberNexts.size());
for (size_t i = 0; i < subscriberNexts.size(); i++) {
auto& suggestion = subscriberNexts[i];
auto& proposal = debugNexts[i];
EXPECT_EQ(suggestion->display.headline, proposal.display.headline);
EXPECT_EQ(suggestion->display.subheadline, proposal.display.subheadline);
EXPECT_EQ(suggestion->display.details, proposal.display.details);
}
}
private:
modular::TestSuggestionListener listener_;
modular::TestDebugNextListener debug_listener_;
fidl::Binding<fuchsia::modular::NextListener> listener_binding_;
fidl::Binding<fuchsia::modular::NextProposalListener> debug_listener_binding_;
};
class ResultCountTest : public NextTest {
public:
void SetUp() override {
NextTest::SetUp();
pub_.reset(new NWriter(context_engine()));
sub_.reset(new NProposals(context_engine(), suggestion_engine()));
}
protected:
// Publishes signals for n new suggestions to context.
void PublishNewSignal(int n = 1) { pub_->Publish(n_ += n); }
private:
std::unique_ptr<NWriter> pub_;
std::unique_ptr<NProposals> sub_;
int n_ = 0;
};
} // namespace
// Macro rather than method to capture the expectation in the assertion message.
#define CHECK_RESULT_COUNT(expected) ASYNC_EQ(expected, suggestion_count())
TEST_F(ResultCountTest, InitiallyEmpty) {
StartListening(10);
WaitUntilIdle();
EXPECT_EQ(0, suggestion_count());
}
TEST_F(ResultCountTest, OneByOne) {
StartListening(10);
PublishNewSignal();
WaitUntilIdle();
EXPECT_EQ(1, suggestion_count());
PublishNewSignal();
WaitUntilIdle();
EXPECT_EQ(2, suggestion_count());
}
TEST_F(ResultCountTest, AddOverLimit) {
StartListening(0);
PublishNewSignal(3);
WaitUntilIdle();
EXPECT_EQ(0, suggestion_count());
SetResultCount(1);
WaitUntilIdle();
EXPECT_EQ(1, suggestion_count());
SetResultCount(3);
WaitUntilIdle();
EXPECT_EQ(3, suggestion_count());
SetResultCount(5);
WaitUntilIdle();
EXPECT_EQ(3, suggestion_count());
PublishNewSignal(4);
WaitUntilIdle();
EXPECT_EQ(5, suggestion_count());
}
TEST_F(ResultCountTest, Clear) {
StartListening(10);
PublishNewSignal(3);
WaitUntilIdle();
EXPECT_EQ(3, suggestion_count());
SetResultCount(0);
WaitUntilIdle();
EXPECT_EQ(0, suggestion_count());
SetResultCount(10);
WaitUntilIdle();
EXPECT_EQ(3, suggestion_count());
}
TEST_F(ResultCountTest, MultiRemove) {
StartListening(10);
PublishNewSignal(3);
WaitUntilIdle();
EXPECT_EQ(3, suggestion_count());
SetResultCount(1);
WaitUntilIdle();
EXPECT_EQ(1, suggestion_count());
SetResultCount(10);
WaitUntilIdle();
EXPECT_EQ(3, suggestion_count());
}
// Tests the removal of earlier suggestions, ensuring that suggestion engine can
// handle the case where an agent requests the removal of suggestions in a non-
// LIFO ordering. This exercises some internal shuffling, especially when
// rankings are likewise non-LIFO (where last = lowest-priority).
//
// TODO(rosswang): Currently this test also tests removing higher-ranked
// suggestions. After we have real ranking, add a test for that.
TEST_F(NextTest, Fifo) {
Proposinator fifo(suggestion_engine());
StartListening(10);
fifo.Propose("1");
WaitUntilIdle();
EXPECT_EQ(1, suggestion_count());
auto uuid_1 = GetOnlySuggestion()->uuid;
fifo.Propose("2");
WaitUntilIdle();
EXPECT_EQ(2, suggestion_count());
fifo.Remove("1");
WaitUntilIdle();
EXPECT_EQ(1, suggestion_count());
auto suggestion = GetOnlySuggestion();
EXPECT_NE(uuid_1, suggestion->uuid);
EXPECT_EQ("2", suggestion->display.headline);
}
// Tests the removal of earlier suggestions while capped.
// TODO(rosswang): see above TODO
TEST_F(NextTest, CappedFifo) {
Proposinator fifo(suggestion_engine());
StartListening(1);
fifo.Propose("1");
WaitUntilIdle();
EXPECT_EQ(1, suggestion_count());
auto uuid1 = GetOnlySuggestion()->uuid;
fifo.Propose("2");
WaitUntilIdle();
EXPECT_EQ(uuid1, GetOnlySuggestion()->uuid)
<< "fuchsia::modular::Proposal 2 ranked over proposal 2; test invalid; "
"update to test "
"FIFO-ranked proposals.";
fifo.Remove("1");
WaitUntilIdle();
ASSERT_EQ(1, suggestion_count());
EXPECT_NE(uuid1, GetOnlySuggestion()->uuid);
EXPECT_EQ("2", GetOnlySuggestion()->display.headline);
}
TEST_F(NextTest, RemoveBeforeSubscribe) {
Proposinator zombinator(suggestion_engine());
zombinator.Propose("brains");
zombinator.Remove("brains");
WaitUntilIdle();
StartListening(10);
WaitUntilIdle();
EXPECT_EQ(0, suggestion_count());
}
TEST_F(NextTest, SubscribeBeyondController) {
Proposinator p(suggestion_engine());
StartListening(10);
WaitUntilIdle();
p.Propose("1");
p.Propose("2");
WaitUntilIdle();
EXPECT_EQ(2, suggestion_count());
}
TEST_F(AskTest, DefaultAsk) {
AskProposinator p(suggestion_engine(), &loop_);
Query("test query");
p.WaitForQuery();
p.ProposeForAsk("1");
p.Commit();
WaitUntilIdle();
EXPECT_EQ(1, suggestion_count());
Query("test query 2");
p.WaitForQuery();
p.ProposeForAsk("2");
p.Commit();
WaitUntilIdle();
EXPECT_EQ(1, suggestion_count());
EnsureDebugMatches();
}
/* These tests assume that a string match between the proposal headline
and the query text factors into suggestion ranking. That ranking
feature is currently turned off and thus these tests fail, but they
will pass with it turned on.
#define CHECK_TOP_HEADLINE(h) \
ASYNC_CHECK(listener()->GetTopSuggestion()->display->headline == h)
TEST_F(AskTest, AskDifferentQueries) {
AskProposinator p(suggestion_engine(), &loop_);
Query("The Hottest Band on the Internet");
p.WaitForQuery();
p.ProposeForAsk("Mozart's Ghost");
p.ProposeForAsk("The Hottest Band on the Internet");
p.Commit();
WaitUntilIdle();
CHECK_TOP_HEADLINE("The Hottest Band on the Internet");
Query("Mozart's Ghost");
p.WaitForQuery();
p.ProposeForAsk("Mozart's Ghost");
p.ProposeForAsk("The Hottest Band on the Internet");
p.Commit();
WaitUntilIdle();
CHECK_TOP_HEADLINE("Mozart's Ghost");
EnsureDebugMatches();
}
TEST_F(AskTest, ChangeHeadlineRank) {
AskProposinator p(suggestion_engine(), &loop_);
Query("test query");
p.WaitForQuery();
p.ProposeForAsk("E-mail", "E-mail");
p.ProposeForAsk("E-vite", "E-vite");
p.ProposeForAsk("E-card", "E-card");
p.ProposeForAsk("Music", "Music");
p.Commit();
WaitUntilIdle();
EXPECT_EQ(4, suggestion_count());
Query("Ca");
p.WaitForQuery();
p.ProposeForAsk("E-mail", "E-mail");
p.ProposeForAsk("E-vite", "E-vite");
p.ProposeForAsk("E-card", "E-card");
p.ProposeForAsk("Music", "Music");
p.Commit();
WaitUntilIdle();
// E-card has a 'ca' in the 3rd position, so should be ranked highest.
CHECK_TOP_HEADLINE("E-card");
Query("Ca");
p.WaitForQuery();
p.ProposeForAsk("E-mail", "E-mail");
p.ProposeForAsk("E-mail", "Cam");
p.ProposeForAsk("E-vite", "E-vite");
p.ProposeForAsk("E-card", "E-card");
p.ProposeForAsk("Music", "Music");
p.Commit();
WaitUntilIdle();
CHECK_TOP_HEADLINE("Cam");
EnsureDebugMatches();
EXPECT_EQ(4, suggestion_count());
}
*/
/* These tests make an assumption that timestamp factors into ranking, which
it no longer does. It could be re-enabled if that factor is included again.
#define HEADLINE_EQ(expected, index) \
EXPECT_EQ(expected, (*listener())[index]->display->headline)
TEST_F(AskTest, AskRanking) {
AskProposinator p(suggestion_engine(), &loop_);
Query("");
p.WaitForQuery();
p.ProposeForAsk("View E-mail");
p.ProposeForAsk("Compose E-mail");
p.ProposeForAsk("Reply to E-mail");
p.ProposeForAsk("Send E-vites");
p.ProposeForAsk("E-mail Guests");
p.Commit();
WaitUntilIdle();
EXPECT_EQ(5, suggestion_count());
// Results should be ranked by timestamp at this point.
HEADLINE_EQ("View E-mail", 0);
HEADLINE_EQ("Compose E-mail", 1);
HEADLINE_EQ("Reply to E-mail", 2);
HEADLINE_EQ("Send E-vites", 3);
HEADLINE_EQ("E-mail Guests", 4);
EnsureDebugMatches();
Query("e-mail");
p.WaitForQuery();
p.ProposeForAsk("View E-mail");
p.ProposeForAsk("Compose E-mail");
p.ProposeForAsk("Reply to E-mail");
p.ProposeForAsk("Send E-vites");
p.ProposeForAsk("E-mail Guests");
p.Commit();
WaitUntilIdle();
EXPECT_EQ(5, suggestion_count());
HEADLINE_EQ("View E-mail", 0);
HEADLINE_EQ("E-mail Guests", 1);
HEADLINE_EQ("Compose E-mail", 2);
HEADLINE_EQ("Reply to E-mail", 3);
EnsureDebugMatches();
Query("e-mail", 2);
p.WaitForQuery();
p.ProposeForAsk("View E-mail");
p.ProposeForAsk("Compose E-mail");
p.ProposeForAsk("Reply to E-mail");
p.ProposeForAsk("Send E-vites");
p.ProposeForAsk("E-mail Guests");
p.Commit();
WaitUntilIdle();
EXPECT_EQ(2, suggestion_count());
HEADLINE_EQ("View E-mail", 0);
HEADLINE_EQ("E-mail Guests", 1);
Query("Compose", 1);
p.WaitForQuery();
p.ProposeForAsk("View E-mail");
p.ProposeForAsk("Compose E-mail");
p.ProposeForAsk("Reply to E-mail");
p.ProposeForAsk("Send E-vites");
p.ProposeForAsk("E-mail Guests");
p.Commit();
WaitUntilIdle();
EXPECT_EQ(1, suggestion_count());
HEADLINE_EQ("Compose E-mail", 0);
EnsureDebugMatches();
}
*/
// This test ensures that Suggestion Engine doesn't keep querying dead handlers.
TEST_F(AskTest, DeadAgent) {
AskProposinator p(suggestion_engine(), &loop_);
{ AskProposinator m(suggestion_engine(), &loop_); }
Query("test query");
p.WaitForQuery();
p.ProposeForAsk("1");
p.Commit();
// RunUntilIdle rather than WaitUntilIdle to not wait for the query handler
// timeout. If we'd need to wait for the timeout, the test should fail.
RunUntilIdle();
EXPECT_EQ(1, suggestion_count());
EXPECT_TRUE(listener()->query_complete());
}
// This test ensures that if a query handler dies while handling a query,
// Suggestion Engine stops waiting for it gracefully.
TEST_F(AskTest, AgentKilledInAction) {
AskProposinator p(suggestion_engine(), &loop_);
{
AskProposinator coulson(suggestion_engine(), &loop_);
Query("test query");
p.WaitForQuery();
p.ProposeForAsk("1");
p.Commit();
RunUntilIdle();
EXPECT_FALSE(listener()->query_complete());
}
RunUntilIdle();
EXPECT_EQ(1, suggestion_count());
EXPECT_TRUE(listener()->query_complete());
}
class SuggestionFilteringTest : public NextTest {};
TEST_F(SuggestionFilteringTest, Baseline) {
// Show that without any existing Stories, we see Proposals to launch
// any story.
Proposinator p(suggestion_engine());
StartListening(10);
fuchsia::modular::Intent intent;
intent.handler = "foo://bar";
fuchsia::modular::AddMod add_mod;
add_mod.mod_name_transitional = "foo";
add_mod.intent = std::move(intent);
fuchsia::modular::StoryCommand command;
command.set_add_mod(std::move(add_mod));
std::vector<fuchsia::modular::StoryCommand> commands;
commands.push_back(std::move(command));
p.Propose("1", std::move(commands));
WaitUntilIdle();
EXPECT_EQ(1, suggestion_count());
}
TEST_F(SuggestionFilteringTest, Baseline_FilterDoesntMatch) {
// Show that with an existing Story for a URL, we see Proposals to launch
// other URLs.
Proposinator p(suggestion_engine());
StartListening(10);
// First notify watchers of the fuchsia::modular::StoryProvider that a story
// already exists.
fuchsia::modular::StoryInfo story_info;
story_info.url = "foo://bazzle_dazzle";
story_info.id = "";
story_provider()->NotifyStoryChanged(
std::move(story_info), fuchsia::modular::StoryState::STOPPED,
fuchsia::modular::StoryVisibilityState::DEFAULT);
fuchsia::modular::Intent intent;
intent.handler = "foo://bar";
fuchsia::modular::AddMod add_mod;
add_mod.intent = std::move(intent);
add_mod.mod_name_transitional = "foo";
fuchsia::modular::StoryCommand command;
command.set_add_mod(std::move(add_mod));
std::vector<fuchsia::modular::StoryCommand> commands;
commands.push_back(std::move(command));
p.Propose("1", std::move(commands));
WaitUntilIdle();
EXPECT_EQ(1, suggestion_count());
}
TEST_F(InterruptionTest, SingleInterruption) {
Proposinator p(suggestion_engine());
p.Propose("1", "2", fuchsia::modular::AnnoyanceType::INTERRUPT);
WaitUntilIdle();
EXPECT_EQ(1, suggestion_count());
EnsureDebugMatches();
}
TEST_F(InterruptionTest, RemovedInterruption) {
Proposinator p(suggestion_engine());
p.Propose("1", "2", fuchsia::modular::AnnoyanceType::INTERRUPT);
WaitUntilIdle();
EXPECT_EQ(1, suggestion_count());
EnsureDebugMatches();
// Removing shouldn't do anything to an interruption
p.Remove("1");
WaitUntilIdle();
EXPECT_EQ(1, suggestion_count());
}
} // namespace maxwell