Commit bbf2c994 authored by Maggie Cai's avatar Maggie Cai Committed by Commit Bot

[IntentHandling] Add preferred apps class in App Service.

This CL adds preferred apps class that contains preferred apps storage
struct, and add and find methods for the struct. Unit tests are also
included.

BUG=853604

Change-Id: I0c2c6c219720da5b56b1f1ed5e3818c760566487
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1864495
Commit-Queue: Maggie Cai <mxcai@chromium.org>
Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Reviewed-by: default avatarNancy Wang <nancylingwang@chromium.org>
Cr-Commit-Position: refs/heads/master@{#708490}
parent 54d46c60
......@@ -3563,6 +3563,7 @@ jumbo_static_library("browser") {
"//chrome/services/app_service/public/cpp:app_update",
"//chrome/services/app_service/public/cpp:icon_loader",
"//chrome/services/app_service/public/cpp:intents",
"//chrome/services/app_service/public/cpp:preferred_apps",
"//components/feedback",
"//components/image_fetcher/core",
"//components/keep_alive_registry",
......
......@@ -53,6 +53,21 @@ source_set("intents") {
"//base",
"//chrome/services/app_service/public/mojom",
"//components/services/app_service/public/cpp:intent_util",
"//url",
]
}
source_set("preferred_apps") {
sources = [
"preferred_apps.cc",
"preferred_apps.h",
]
deps = [
":intents",
"//base",
"//chrome/services/app_service/public/mojom",
"//url",
]
}
......@@ -65,6 +80,7 @@ source_set("unit_tests") {
"icon_cache_unittest.cc",
"icon_coalescer_unittest.cc",
"intent_util_unittest.cc",
"preferred_apps_unittest.cc",
]
deps = [
......
// Copyright 2019 The Chromium 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 "chrome/services/app_service/public/cpp/preferred_apps.h"
#include <utility>
#include <vector>
#include "base/strings/string_util.h"
#include "base/values.h"
#include "chrome/services/app_service/public/cpp/intent_filter_util.h"
#include "chrome/services/app_service/public/cpp/intent_util.h"
#include "url/gurl.h"
namespace {
constexpr char kScheme[] = "scheme";
constexpr char kHost[] = "host";
constexpr char kPattern[] = "pattern";
constexpr char kAppId[] = "app_id";
constexpr char kLiteral[] = "literal";
constexpr char kPrefix[] = "prefix";
constexpr char kGlob[] = "glob";
const char* ConditionTypeToString(apps::mojom::ConditionType condition_type) {
switch (condition_type) {
case apps::mojom::ConditionType::kScheme:
return kScheme;
case apps::mojom::ConditionType::kHost:
return kHost;
case apps::mojom::ConditionType::kPattern:
return kPattern;
}
}
const char* MatchTypeToString(apps::mojom::PatternMatchType match_type) {
switch (match_type) {
case apps::mojom::PatternMatchType::kNone:
return "";
case apps::mojom::PatternMatchType::kLiteral:
return kLiteral;
case apps::mojom::PatternMatchType::kPrefix:
return kPrefix;
case apps::mojom::PatternMatchType::kGlob:
return kGlob;
}
}
apps::mojom::PatternMatchType StringToMatchType(const std::string& match_type) {
if (match_type == kLiteral)
return apps::mojom::PatternMatchType::kLiteral;
else if (match_type == kPrefix)
return apps::mojom::PatternMatchType::kPrefix;
else if (match_type == kGlob)
return apps::mojom::PatternMatchType::kGlob;
else
return apps::mojom::PatternMatchType::kNone;
}
base::Value* FindOrSetDictionary(base::Value* dict, base::StringPiece key) {
auto* newDict = dict->FindKey(key);
if (!newDict) {
newDict = dict->SetKey(key, base::DictionaryValue());
}
return newDict;
}
// Find a match dictionary for a condition type and value pair. Return nullptr
// if cannot find a match.
// For example, to search for https scheme, we need to search "scheme" key
// first, then look for "https" key inside it. And for each condition pair,
// there is a possibility to have an "app_id" key. This will look
// like:
// ...
// {"scheme": {
// "https": {
// "app_id": <app_id>,
// ... <other conditions, e.g. host>
// }
// }
// }
// In this case this function will return the pointer to the dictionary that is
// the value of "https".
base::Value* FindDictionaryForTypeAndValue(
base::Value* dict,
apps::mojom::ConditionType condition_type,
const std::string& value) {
auto* condition_type_dict =
dict->FindKey(ConditionTypeToString(condition_type));
if (!condition_type_dict)
return nullptr;
if (condition_type != apps::mojom::ConditionType::kPattern)
return condition_type_dict->FindKey(value);
// For pattern matching, we need to go through all patterns and match types
// to see if we have a match.
// For example, if we have preferred apps set for same scheme, host, but
// multiple patterns:
// "/abc" literal match, "/abc" prefix match, "/g*h" glob match.
// The representation is going to look like:
// {"/abc": {
// "literal": {
// "app_id": <app_id>
// }
// "prefix": {
// "app_id": <app_id>
// }
// "/g*h": {
// "glob": {
// "app_id": <app_id>
// }
// }
// }
for (const auto& pattern_key_value : condition_type_dict->DictItems()) {
const std::string& pattern = pattern_key_value.first;
base::Value* pattern_dict = &pattern_key_value.second;
DCHECK(pattern_dict);
for (const auto& match_type_key_value : pattern_dict->DictItems()) {
const std::string& match_type = match_type_key_value.first;
if (apps_util::ConditionValueMatches(
value, apps_util::MakeConditionValue(
pattern, StringToMatchType(match_type)))) {
// Find the first match for patterns.
// TODO(crbug.com/853604): Determine best match between patterns.
return &match_type_key_value.second;
}
}
}
return nullptr;
}
// This method finds the dictionary keyed by the a condition_value and updates
// the |best_match_app_id| if there is a better app_id match found. For example,
// the input |dict| is pointing to the top layer of this dictionary, and what we
// found will be the inner dictionary key by https. Also in this case the
// |best_match_app_id| will be updated because there is a app id for this filter
// condition.
// ...
// {"scheme": {
// "https": {
// "app_id": <app_id>,
// ... <other conditions, e.g. host>
// }
// }
// }
base::Value* FindDictAndUpdateBestMatchAppId(
apps::mojom::ConditionType condition_type,
const std::string& value,
base::Value* dict,
base::Optional<std::string>* best_match_app_id) {
auto* found_dict = FindDictionaryForTypeAndValue(dict, condition_type, value);
if (!found_dict)
return found_dict;
std::string* app_id = found_dict->FindStringKey(kAppId);
if (app_id)
*best_match_app_id = *app_id;
return found_dict;
}
// Going through each |condition| in the |conditions| by their |index|.
// Each |condition| can contain multiple |condition_values|, every combination
// between the |condition_values| in the |conditions| should be set for the
// app_id.
// For example, to set a preferred app for an |intent_filter| that support
// scheme http or https, and host www.google.com and www.google.com.au:
// conditions = [
// {
// condition_type: scheme
// condition_values = [
// {value: http, match_type: kNone},
// {value: https, match_type: kNone}
// ]
// },
// {
// condition_type: host
// condition_values = [
// {value: www.google.com, match_type: kNone},
// {value: www.google.com.au, match_type: kNone}
// ]
// }
// ]
//
// In this case, we should store the preferred app for this intent filter in
// this format:
// {"scheme": {
// "http": {
// "host": {
// "www.google.com": {
// "app_id": <app_id>
// },
// "www.google.com.au": {
// "app_id": <app_id>
// },
// },
// "https": {
// "host": {
// "www.google.com": {
// "app_id": <app_id>
// },
// "www.google.com.au": {
// "app_id": <app_id>
// },
// },
// },
// },
// }
void SetPreferredApp(const std::vector<apps::mojom::ConditionPtr>& conditions,
size_t index,
base::Value* dict,
const std::string& app_id) {
// If there are no more condition key to add to the dictionary, we reach the
// base case, set the key for the |app_id|.
if (index == conditions.size()) {
dict->SetStringKey(kAppId, app_id);
return;
}
const auto& condition = conditions[index];
auto* condition_type_dict = FindOrSetDictionary(
dict, ConditionTypeToString(condition->condition_type));
for (const auto& condition_value : condition->condition_values) {
std::string condition_value_key = condition_value->value;
base::Value* condition_value_dictionary;
// For pattern type, use two nested dictionaries to represent the pattern
// and the match type.
if (condition->condition_type == apps::mojom::ConditionType::kPattern) {
auto* match_type_dict =
FindOrSetDictionary(condition_type_dict, condition_value_key);
condition_value_dictionary = FindOrSetDictionary(
match_type_dict, MatchTypeToString(condition_value->match_type));
} else {
condition_value_dictionary =
FindOrSetDictionary(condition_type_dict, condition_value_key);
}
// For each |condition_value|, add dictionary for the following conditions.
SetPreferredApp(conditions, index + 1, condition_value_dictionary, app_id);
}
}
} // namespace
namespace apps {
PreferredApps::PreferredApps() = default;
PreferredApps::~PreferredApps() = default;
// static
// Recursively verifies that the structure of |value| matches what we expect.
// |value| should be a dictionary where each item is either:
// * key == kAppId and a string value, or
// * some other string value with a dictionary value.
bool PreferredApps::VerifyPreferredApps(base::Value* value) {
if (!value->is_dict())
return false;
bool all_items_valid = true;
for (const auto& key_value : value->DictItems()) {
bool item_valid = false;
if (key_value.first == kAppId)
item_valid = key_value.second.is_string();
else
item_valid = VerifyPreferredApps(&key_value.second);
if (!item_valid) {
all_items_valid = false;
break;
}
}
return all_items_valid;
}
void PreferredApps::Init(std::unique_ptr<base::Value> preferred_apps) {
if (preferred_apps && VerifyPreferredApps(preferred_apps.get())) {
preferred_apps_ = std::move(preferred_apps);
} else {
preferred_apps_ =
std::make_unique<base::Value>(base::Value::Type::DICTIONARY);
}
}
bool PreferredApps::AddPreferredApp(
const std::string& app_id,
const apps::mojom::IntentFilterPtr& intent_filter) {
if (!preferred_apps_) {
return false;
}
// For an |intent_filter| there could be multiple |conditions|, and for each
// condition, there could be multiple |condition_values|. When we set
// preferred app for and |intent_filter|, we need to add the preferred app for
// all combinations of these |condition_values|.
SetPreferredApp(intent_filter->conditions, 0, preferred_apps_.get(), app_id);
return true;
}
base::Optional<std::string> PreferredApps::FindPreferredAppForIntent(
const apps::mojom::IntentPtr& intent) {
base::Optional<std::string> best_match_app_id = base::nullopt;
if (!preferred_apps_)
return best_match_app_id;
// Currently only support intent that has the full URL.
if (!intent->scheme.has_value() || !intent->host.has_value() ||
!intent->path.has_value()) {
return best_match_app_id;
}
// The best match is the deepest layer the search can reach.
// E.g. a preferred app for scheme and host is better match then preferred
// app set for scheme only.
auto* scheme_dict = FindDictAndUpdateBestMatchAppId(
apps::mojom::ConditionType::kScheme, intent->scheme.value(),
preferred_apps_.get(), &best_match_app_id);
if (!scheme_dict)
return best_match_app_id;
auto* host_dict = FindDictAndUpdateBestMatchAppId(
apps::mojom::ConditionType::kHost, intent->host.value(), scheme_dict,
&best_match_app_id);
if (!host_dict)
return best_match_app_id;
FindDictAndUpdateBestMatchAppId(apps::mojom::ConditionType::kPattern,
intent->path.value(), host_dict,
&best_match_app_id);
return best_match_app_id;
}
base::Optional<std::string> PreferredApps::FindPreferredAppForUrl(
const GURL& url) {
auto intent = apps_util::CreateIntentFromUrl(url);
return FindPreferredAppForIntent(intent);
}
} // namespace apps
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_SERVICES_APP_SERVICE_PUBLIC_CPP_PREFERRED_APPS_H_
#define CHROME_SERVICES_APP_SERVICE_PUBLIC_CPP_PREFERRED_APPS_H_
#include <memory>
#include <string>
#include "base/optional.h"
#include "chrome/services/app_service/public/mojom/types.mojom.h"
class GURL;
namespace base {
class Value;
}
namespace apps {
// The preferred apps set by the user. The preferred apps is stored as
// base::DictionaryValue. It is a nested map that maps the intent filter
// condition type and value to app id. For example, to represent a
// preferred app for an intent filter that handles https://www.google.com,
// and a preferred app for an intent filter that handles tel:// link,
// the preferred_apps dictionary will look like:
// {“scheme”: {
// "https”: {
// “host”: {
// “www.google.com”: {
// "app_id": <app_id>
// },
// },
// },
// "tel": {
// "app_id": <app_id>
// },
// },
// }
class PreferredApps {
public:
PreferredApps();
~PreferredApps();
static bool VerifyPreferredApps(base::Value* dict);
void Init(std::unique_ptr<base::Value> preferred_apps);
// Add a preferred app for an |intent_filter|.
bool AddPreferredApp(const std::string& app_id,
const apps::mojom::IntentFilterPtr& intent_filter);
// Find preferred app id for an |intent|.
base::Optional<std::string> FindPreferredAppForIntent(
const apps::mojom::IntentPtr& intent);
// Find preferred app id for an |url|.
base::Optional<std::string> FindPreferredAppForUrl(const GURL& url);
private:
std::unique_ptr<base::Value> preferred_apps_;
DISALLOW_COPY_AND_ASSIGN(PreferredApps);
};
} // namespace apps
#endif // CHROME_SERVICES_APP_SERVICE_PUBLIC_CPP_PREFERRED_APPS_H_
// Copyright 2019 The Chromium 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 "chrome/services/app_service/public/cpp/preferred_apps.h"
#include "base/values.h"
#include "chrome/services/app_service/public/cpp/intent_filter_util.h"
#include "chrome/services/app_service/public/cpp/intent_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
const char kAppId1[] = "abcdefg";
const char kAppId2[] = "gfedcba";
const char kAppId3[] = "hahahahaha";
const char kAppIdKey[] = "app_id";
} // namespace
class PreferredAppTest : public testing::Test {
protected:
apps::mojom::IntentFilterPtr CreateSchemeOnlyFilter(
const std::string& scheme) {
std::vector<apps::mojom::ConditionValuePtr> condition_values;
condition_values.push_back(apps_util::MakeConditionValue(
scheme, apps::mojom::PatternMatchType::kNone));
auto condition = apps_util::MakeCondition(
apps::mojom::ConditionType::kScheme, std::move(condition_values));
auto intent_filter = apps::mojom::IntentFilter::New();
intent_filter->conditions.push_back(std::move(condition));
return intent_filter;
}
apps::mojom::IntentFilterPtr CreateSchemeAndHostOnlyFilter(
const std::string& scheme,
const std::string& host) {
std::vector<apps::mojom::ConditionValuePtr> scheme_condition_values;
scheme_condition_values.push_back(apps_util::MakeConditionValue(
scheme, apps::mojom::PatternMatchType::kNone));
auto scheme_condition =
apps_util::MakeCondition(apps::mojom::ConditionType::kScheme,
std::move(scheme_condition_values));
std::vector<apps::mojom::ConditionValuePtr> host_condition_values;
host_condition_values.push_back(apps_util::MakeConditionValue(
host, apps::mojom::PatternMatchType::kNone));
auto host_condition = apps_util::MakeCondition(
apps::mojom::ConditionType::kHost, std::move(host_condition_values));
auto intent_filter = apps::mojom::IntentFilter::New();
intent_filter->conditions.push_back(std::move(scheme_condition));
intent_filter->conditions.push_back(std::move(host_condition));
return intent_filter;
}
apps::PreferredApps preferred_apps_;
};
// Test that for a single preferred app with URL filter, we can add
// and find (or not find) the correct preferred app id for different
// URLs.
TEST_F(PreferredAppTest, AddPreferredAppForURL) {
preferred_apps_.Init(
std::make_unique<base::Value>(base::Value::Type::DICTIONARY));
GURL filter_url = GURL("https://www.google.com/abc");
auto intent_filter = apps_util::CreateIntentFilterForUrlScope(filter_url);
preferred_apps_.AddPreferredApp(kAppId1, intent_filter);
EXPECT_EQ(kAppId1, preferred_apps_.FindPreferredAppForUrl(filter_url));
GURL url_in_scope = GURL("https://www.google.com/abcde");
EXPECT_EQ(kAppId1, preferred_apps_.FindPreferredAppForUrl(url_in_scope));
GURL url_wrong_scheme = GURL("tel://www.google.com/");
EXPECT_EQ(base::nullopt,
preferred_apps_.FindPreferredAppForUrl(url_wrong_scheme));
GURL url_wrong_host = GURL("https://www.hahaha.com/");
EXPECT_EQ(base::nullopt,
preferred_apps_.FindPreferredAppForUrl(url_wrong_host));
GURL url_not_in_scope = GURL("https://www.google.com/a");
EXPECT_EQ(base::nullopt,
preferred_apps_.FindPreferredAppForUrl(url_not_in_scope));
}
// Test for preferred app with filter that does not have all condition
// types. E.g. add preferred app with intent filter that only have scheme.
TEST_F(PreferredAppTest, TopLayerFilters) {
preferred_apps_.Init(
std::make_unique<base::Value>(base::Value::Type::DICTIONARY));
auto intent_filter = CreateSchemeOnlyFilter("tel");
preferred_apps_.AddPreferredApp(kAppId1, intent_filter);
GURL url_in_scope = GURL("tel://1234556/");
EXPECT_EQ(kAppId1, preferred_apps_.FindPreferredAppForUrl(url_in_scope));
GURL url_not_in_scope = GURL("http://www.google.com");
EXPECT_EQ(base::nullopt,
preferred_apps_.FindPreferredAppForUrl(url_not_in_scope));
}
// Test for multiple preferred app setting with different number of condition
// types.
TEST_F(PreferredAppTest, MixLayerFilters) {
preferred_apps_.Init(
std::make_unique<base::Value>(base::Value::Type::DICTIONARY));
auto intent_filter_scheme = CreateSchemeOnlyFilter("tel");
preferred_apps_.AddPreferredApp(kAppId1, intent_filter_scheme);
auto intent_filter_scheme_host =
CreateSchemeAndHostOnlyFilter("http", "www.abc.com");
preferred_apps_.AddPreferredApp(kAppId2, intent_filter_scheme_host);
auto intent_filter_url =
apps_util::CreateIntentFilterForUrlScope(GURL("https://www.google.com/"));
preferred_apps_.AddPreferredApp(kAppId3, intent_filter_url);
GURL url_1 = GURL("tel://1234556/");
GURL url_2 = GURL("http://www.abc.com/");
GURL url_3 = GURL("https://www.google.com/");
GURL url_out_scope = GURL("https://www.abc.com/");
EXPECT_EQ(kAppId1, preferred_apps_.FindPreferredAppForUrl(url_1));
EXPECT_EQ(kAppId2, preferred_apps_.FindPreferredAppForUrl(url_2));
EXPECT_EQ(kAppId3, preferred_apps_.FindPreferredAppForUrl(url_3));
EXPECT_EQ(base::nullopt,
preferred_apps_.FindPreferredAppForUrl(url_out_scope));
}
// Test that when there are multiple preferred apps for one intent, the best
// matching one will be picked.
TEST_F(PreferredAppTest, MultiplePreferredApps) {
preferred_apps_.Init(
std::make_unique<base::Value>(base::Value::Type::DICTIONARY));
GURL url = GURL("https://www.google.com/");
auto intent_filter_scheme = CreateSchemeOnlyFilter("https");
preferred_apps_.AddPreferredApp(kAppId1, intent_filter_scheme);
EXPECT_EQ(kAppId1, preferred_apps_.FindPreferredAppForUrl(url));
auto intent_filter_scheme_host =
CreateSchemeAndHostOnlyFilter("https", "www.google.com");
preferred_apps_.AddPreferredApp(kAppId2, intent_filter_scheme_host);
EXPECT_EQ(kAppId2, preferred_apps_.FindPreferredAppForUrl(url));
auto intent_filter_url =
apps_util::CreateIntentFilterForUrlScope(GURL("https://www.google.com/"));
preferred_apps_.AddPreferredApp(kAppId3, intent_filter_url);
EXPECT_EQ(kAppId3, preferred_apps_.FindPreferredAppForUrl(url));
}
// Test that we can properly add and search for filters that has multiple
// condition values for a condition type.
TEST_F(PreferredAppTest, MultipleConditionValues) {
preferred_apps_.Init(
std::make_unique<base::Value>(base::Value::Type::DICTIONARY));
auto intent_filter =
apps_util::CreateIntentFilterForUrlScope(GURL("https://www.google.com/"));
intent_filter->conditions[0]->condition_values.push_back(
apps_util::MakeConditionValue("http",
apps::mojom::PatternMatchType::kNone));
preferred_apps_.AddPreferredApp(kAppId1, intent_filter);
GURL url_https = GURL("https://www.google.com/");
GURL url_http = GURL("http://www.google.com/");
GURL url_http_out_of_scope = GURL("http://www.abc.com/");
GURL url_wrong_scheme = GURL("tel://1234567/");
EXPECT_EQ(kAppId1, preferred_apps_.FindPreferredAppForUrl(url_https));
EXPECT_EQ(kAppId1, preferred_apps_.FindPreferredAppForUrl(url_http));
EXPECT_EQ(base::nullopt,
preferred_apps_.FindPreferredAppForUrl(url_http_out_of_scope));
EXPECT_EQ(base::nullopt,
preferred_apps_.FindPreferredAppForUrl(url_wrong_scheme));
}
// Test for more than one pattern available, we can find the correct match.
TEST_F(PreferredAppTest, DifferentPatterns) {
preferred_apps_.Init(
std::make_unique<base::Value>(base::Value::Type::DICTIONARY));
auto intent_filter = CreateSchemeAndHostOnlyFilter("https", "www.google.com");
auto pattern_condition =
apps_util::MakeCondition(apps::mojom::ConditionType::kPattern,
std::vector<apps::mojom::ConditionValuePtr>());
intent_filter->conditions.push_back(std::move(pattern_condition));
auto condition_value_literal = apps_util::MakeConditionValue(
"/bc", apps::mojom::PatternMatchType::kLiteral);
auto condition_value_prefix = apps_util::MakeConditionValue(
"/a", apps::mojom::PatternMatchType::kPrefix);
auto condition_value_glob = apps_util::MakeConditionValue(
"/c.*d", apps::mojom::PatternMatchType::kGlob);
intent_filter->conditions[2]->condition_values.push_back(
std::move(condition_value_literal));
preferred_apps_.AddPreferredApp(kAppId1, intent_filter);
intent_filter->conditions[2]->condition_values.clear();
intent_filter->conditions[2]->condition_values.push_back(
std::move(condition_value_prefix));
preferred_apps_.AddPreferredApp(kAppId2, intent_filter);
intent_filter->conditions[2]->condition_values.clear();
intent_filter->conditions[2]->condition_values.push_back(
std::move(condition_value_glob));
preferred_apps_.AddPreferredApp(kAppId3, intent_filter);
GURL url_1 = GURL("https://www.google.com/bc");
GURL url_2 = GURL("https://www.google.com/abbb");
GURL url_3 = GURL("https://www.google.com/ccccccd");
GURL url_out_scope = GURL("https://www.google.com/dfg");
EXPECT_EQ(kAppId1, preferred_apps_.FindPreferredAppForUrl(url_1));
EXPECT_EQ(kAppId2, preferred_apps_.FindPreferredAppForUrl(url_2));
EXPECT_EQ(kAppId3, preferred_apps_.FindPreferredAppForUrl(url_3));
EXPECT_EQ(base::nullopt,
preferred_apps_.FindPreferredAppForUrl(url_out_scope));
}
// Test that for same intent filter, the app id will overwrite the old setting.
TEST_F(PreferredAppTest, OverwritePreferredApp) {
preferred_apps_.Init(
std::make_unique<base::Value>(base::Value::Type::DICTIONARY));
GURL filter_url = GURL("https://www.google.com/abc");
auto intent_filter = apps_util::CreateIntentFilterForUrlScope(filter_url);
preferred_apps_.AddPreferredApp(kAppId1, intent_filter);
EXPECT_EQ(kAppId1, preferred_apps_.FindPreferredAppForUrl(filter_url));
preferred_apps_.AddPreferredApp(kAppId2, intent_filter);
EXPECT_EQ(kAppId2, preferred_apps_.FindPreferredAppForUrl(filter_url));
}
// Test the preferred apps dictionary structure is as expected.
TEST_F(PreferredAppTest, VerifyPreferredApps) {
base::Value preferred_app = base::DictionaryValue();
EXPECT_TRUE(apps::PreferredApps::VerifyPreferredApps(&preferred_app));
auto* abc_dict = preferred_app.SetKey("abc", base::DictionaryValue());
auto* efg_dict = preferred_app.SetKey("efg", base::DictionaryValue());
auto* hij_dict = abc_dict->SetKey("hij", base::DictionaryValue());
hij_dict->SetStringKey(kAppIdKey, kAppId1);
EXPECT_TRUE(apps::PreferredApps::VerifyPreferredApps(&preferred_app));
// Test if there is a app id key with non string value.
efg_dict->SetKey(kAppIdKey, base::DictionaryValue());
EXPECT_FALSE(apps::PreferredApps::VerifyPreferredApps(&preferred_app));
efg_dict->SetStringKey(kAppIdKey, kAppId2);
EXPECT_TRUE(apps::PreferredApps::VerifyPreferredApps(&preferred_app));
efg_dict->RemoveKey(kAppIdKey);
efg_dict->SetKey("abced", base::DictionaryValue());
EXPECT_TRUE(apps::PreferredApps::VerifyPreferredApps(&preferred_app));
// Test if there is a non app id key with string value.
efg_dict->SetStringKey("abced", "fgijk");
EXPECT_FALSE(apps::PreferredApps::VerifyPreferredApps(&preferred_app));
// Test having other value types in the dictionary.
efg_dict->RemoveKey("abced");
efg_dict->SetKey("klm", base::ListValue());
EXPECT_FALSE(apps::PreferredApps::VerifyPreferredApps(&preferred_app));
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment