[go: nahoru, domu]

blob: 132bb6e3f8e2229c291cf53612948fb3a1f5e08e [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/origin_trials/browser/leveldb_persistence_provider.h"
#include <map>
#include <memory>
#include "base/containers/span.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_piece.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/time/clock.h"
#include "components/leveldb_proto/public/proto_database.h"
#include "components/leveldb_proto/public/proto_database_provider.h"
#include "components/leveldb_proto/testing/fake_db.h"
#include "components/origin_trials/common/persisted_trial_token.h"
#include "components/origin_trials/proto/db_trial_token.pb.h"
#include "components/origin_trials/proto/proto_util.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/origin_trials/scoped_test_origin_trial_policy.h"
#include "third_party/blink/public/common/origin_trials/trial_token.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace origin_trials {
namespace {
const char kExampleComOrigin[] = "https://example.com";
const char kSecondaryExampleComOrigin[] = "https://secondary.example.com";
const char kTrialName[] = "FrobulatePersistent";
const char kTrialSignature[] = "trial signature";
const char kTrialSignatureAlternate[] = "alternate trial signature";
const char kExampleComDomain[] = "https://example.com";
const char kGoogleComDomain[] = "https://google.com";
using leveldb_proto::test::FakeDB;
class LevelDbPersistenceProviderUnitTest : public testing::Test {
public:
LevelDbPersistenceProviderUnitTest()
: task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
LevelDbPersistenceProviderUnitTest(
const LevelDbPersistenceProviderUnitTest&) = delete;
LevelDbPersistenceProviderUnitTest& operator=(
const LevelDbPersistenceProviderUnitTest&) = delete;
~LevelDbPersistenceProviderUnitTest() override = default;
void CreatePersistenceProvider() {
std::unique_ptr<FakeDB<origin_trials_pb::TrialTokenDbEntries>> db =
std::make_unique<FakeDB<origin_trials_pb::TrialTokenDbEntries>>(
&db_entries_);
fake_db_unretained_ = db.get();
persistence_provider_ =
LevelDbPersistenceProvider::CreateForTesting(std::move(db));
;
}
void InitLevelDb(bool ok) {
fake_db_unretained_->InitStatusCallback(ok ? leveldb_proto::Enums::kOK
: leveldb_proto::Enums::kError);
}
void FlushLoadCallback(bool success) {
fake_db_unretained_->LoadCallback(success);
task_environment_.RunUntilIdle(); // Run the DB transformation callback
}
void FlushUpdateCallback() { fake_db_unretained_->UpdateCallback(true); }
void InitPersistenceProvider() {
CreatePersistenceProvider();
InitLevelDb(true);
FlushLoadCallback(true);
}
protected:
blink::ScopedTestOriginTrialPolicy origin_trial_policy_;
content::BrowserTaskEnvironment task_environment_;
std::map<std::string, origin_trials_pb::TrialTokenDbEntries> db_entries_;
base::raw_ptr<FakeDB<origin_trials_pb::TrialTokenDbEntries>>
fake_db_unretained_;
std::unique_ptr<LevelDbPersistenceProvider> persistence_provider_;
};
TEST_F(LevelDbPersistenceProviderUnitTest, NormalStartupLogsHistograms) {
base::HistogramTester ht;
InitPersistenceProvider();
ht.ExpectUniqueSample("OriginTrials.PersistentOriginTrial.LevelDbInitSuccess",
true, 1);
ht.ExpectUniqueSample("OriginTrials.PersistentOriginTrial.LevelDbLoadSuccess",
true, 1);
ht.ExpectUniqueSample(
"OriginTrials.PersistentOriginTrial.OriginsAddedBeforeDbLoad", 0, 1);
ht.ExpectUniqueSample(
"OriginTrials.PersistentOriginTrial.OriginLookupsBeforeDbLoad", 0, 1);
ht.ExpectUniqueSample("OriginTrials.PersistentOriginTrial.LevelDbLoadSize", 0,
1);
ht.ExpectTotalCount("OriginTrials.PersistentOriginTrial.LevelDbLoadTime", 1);
ht.ExpectTotalCount("OriginTrials.PersistentOriginTrial.PartitionSetSize", 0);
}
TEST_F(LevelDbPersistenceProviderUnitTest, FailedInitLogsHistograms) {
base::HistogramTester ht;
CreatePersistenceProvider();
InitLevelDb(false);
// We only expect the init success histogram to be logged in case of init
// failure
ht.ExpectUniqueSample("OriginTrials.PersistentOriginTrial.LevelDbInitSuccess",
false, 1);
ht.ExpectTotalCount("OriginTrials.PersistentOriginTrial.LevelDbLoadSuccess",
0);
ht.ExpectTotalCount(
"OriginTrials.PersistentOriginTrial.OriginsAddedBeforeDbLoad", 0);
ht.ExpectTotalCount(
"OriginTrials.PersistentOriginTrial.OriginLookupsBeforeDbLoad", 0);
ht.ExpectTotalCount("OriginTrials.PersistentOriginTrial.LevelDbLoadSize", 0);
ht.ExpectTotalCount("OriginTrials.PersistentOriginTrial.LevelDbLoadTime", 0);
ht.ExpectTotalCount("OriginTrials.PersistentOriginTrial.PartitionSetSize", 0);
}
TEST_F(LevelDbPersistenceProviderUnitTest, FailedLoadLogsHistograms) {
base::HistogramTester ht;
CreatePersistenceProvider();
InitLevelDb(true);
FlushLoadCallback(false);
// In case of load failure, only the init and load histograms should be
// logged.
ht.ExpectUniqueSample("OriginTrials.PersistentOriginTrial.LevelDbInitSuccess",
true, 1);
ht.ExpectUniqueSample("OriginTrials.PersistentOriginTrial.LevelDbLoadSuccess",
false, 1);
ht.ExpectTotalCount(
"OriginTrials.PersistentOriginTrial.OriginsAddedBeforeDbLoad", 0);
ht.ExpectTotalCount(
"OriginTrials.PersistentOriginTrial.OriginLookupsBeforeDbLoad", 0);
ht.ExpectTotalCount("OriginTrials.PersistentOriginTrial.LevelDbLoadSize", 0);
ht.ExpectTotalCount("OriginTrials.PersistentOriginTrial.LevelDbLoadTime", 0);
ht.ExpectTotalCount("OriginTrials.PersistentOriginTrial.PartitionSetSize", 0);
}
TEST_F(LevelDbPersistenceProviderUnitTest, UpdatesAppliedInMemoryAndToDb) {
InitPersistenceProvider();
url::Origin origin = url::Origin::Create(GURL(kExampleComOrigin));
base::flat_set<std::string> partition_sites = {kExampleComDomain};
base::Time expiry = base::Time::Now() + base::Days(365);
base::flat_set<PersistedTrialToken> tokens;
tokens.emplace(kTrialName, expiry, blink::TrialToken::UsageRestriction::kNone,
kTrialSignature, partition_sites);
persistence_provider_->SavePersistentTrialTokens(origin, tokens);
base::flat_set<PersistedTrialToken> stored_tokens =
persistence_provider_->GetPersistentTrialTokens(origin);
EXPECT_EQ(tokens, stored_tokens);
FlushUpdateCallback();
// Expect the DB to have been updated in the back after an update
EXPECT_EQ(1ul, db_entries_.size());
EXPECT_NE(db_entries_.end(), db_entries_.find(kExampleComOrigin))
<< "Expect to find a value for kExampleComOrigin in the map";
persistence_provider_->ClearPersistedTokens();
stored_tokens = persistence_provider_->GetPersistentTrialTokens(origin);
EXPECT_TRUE(stored_tokens.empty());
FlushUpdateCallback();
EXPECT_TRUE(db_entries_.empty());
}
TEST_F(LevelDbPersistenceProviderUnitTest, TokensLoadedFromDbOnStartup) {
base::HistogramTester ht;
base::Time expiry = base::Time::Now() + base::Days(365);
url::Origin origin_a = url::Origin::Create(GURL(kExampleComOrigin));
base::flat_set<std::string> partition_sites_a = {kExampleComDomain};
base::flat_set<PersistedTrialToken> tokens_a;
tokens_a.emplace(kTrialName, expiry,
blink::TrialToken::UsageRestriction::kNone, kTrialSignature,
partition_sites_a);
db_entries_[origin_a.Serialize()] =
origin_trials_pb::ProtoFromTokens(origin_a, tokens_a);
url::Origin origin_b = url::Origin::Create(GURL(kSecondaryExampleComOrigin));
base::flat_set<std::string> partition_sites_b = {kExampleComDomain,
kGoogleComDomain};
base::flat_set<PersistedTrialToken> tokens_b;
tokens_b.emplace(kTrialName, expiry,
blink::TrialToken::UsageRestriction::kNone, kTrialSignature,
partition_sites_b);
db_entries_[origin_b.Serialize()] =
origin_trials_pb::ProtoFromTokens(origin_b, tokens_b);
InitPersistenceProvider();
// Two items should have been loaded
ht.ExpectUniqueSample("OriginTrials.PersistentOriginTrial.LevelDbLoadSize", 2,
1);
// The PartitionSetSize should be reported for each token individually.
ht.ExpectBucketCount("OriginTrials.PersistentOriginTrial.PartitionSetSize", 1,
1);
ht.ExpectBucketCount("OriginTrials.PersistentOriginTrial.PartitionSetSize", 2,
1);
// Both tokens are in a first-party partition.
ht.ExpectBucketCount(
"OriginTrials.PersistentOriginTrial.TokenHasFirstPartyPartition", true,
2);
// The DB should not have been used before load.
ht.ExpectUniqueSample(
"OriginTrials.PersistentOriginTrial.OriginsAddedBeforeDbLoad", 0, 1);
ht.ExpectUniqueSample(
"OriginTrials.PersistentOriginTrial.OriginLookupsBeforeDbLoad", 0, 1);
EXPECT_EQ(tokens_a,
persistence_provider_->GetPersistentTrialTokens(origin_a));
EXPECT_EQ(tokens_b,
persistence_provider_->GetPersistentTrialTokens(origin_b));
}
TEST_F(LevelDbPersistenceProviderUnitTest,
TokensLoadedFromDbOnStartupAreCleanedUpIfExpired) {
base::HistogramTester ht;
url::Origin origin = url::Origin::Create(GURL(kExampleComOrigin));
base::flat_set<std::string> partition_sites = {kExampleComDomain};
base::Time expiry = base::Time::Now() - base::Days(1);
base::flat_set<PersistedTrialToken> tokens;
tokens.emplace(kTrialName, expiry, blink::TrialToken::UsageRestriction::kNone,
kTrialSignature, partition_sites);
db_entries_[origin.Serialize()] =
origin_trials_pb::ProtoFromTokens(origin, tokens);
InitPersistenceProvider();
// One item should have been loaded from the database
ht.ExpectUniqueSample("OriginTrials.PersistentOriginTrial.LevelDbLoadSize", 1,
1);
// The DB should not have been used before load
ht.ExpectUniqueSample(
"OriginTrials.PersistentOriginTrial.OriginsAddedBeforeDbLoad", 0, 1);
ht.ExpectUniqueSample(
"OriginTrials.PersistentOriginTrial.OriginLookupsBeforeDbLoad", 0, 1);
base::flat_set<PersistedTrialToken> stored_tokens =
persistence_provider_->GetPersistentTrialTokens(origin);
EXPECT_TRUE(stored_tokens.empty());
FlushUpdateCallback();
EXPECT_TRUE(db_entries_.empty());
}
TEST_F(LevelDbPersistenceProviderUnitTest, QueriesBeforeDbLoad) {
base::HistogramTester ht;
url::Origin origin_a = url::Origin::Create(GURL(kExampleComOrigin));
url::Origin origin_b = url::Origin::Create(GURL(kSecondaryExampleComOrigin));
base::flat_set<std::string> partition_sites = {kExampleComDomain};
base::Time expiry = base::Time::Now() + base::Days(365);
base::flat_set<PersistedTrialToken> tokens_in_db;
tokens_in_db.emplace(kTrialName, expiry,
blink::TrialToken::UsageRestriction::kNone,
kTrialSignature, partition_sites);
db_entries_[origin_a.Serialize()] =
origin_trials_pb::ProtoFromTokens(origin_a, tokens_in_db);
base::flat_set<PersistedTrialToken> tokens_before_load;
tokens_before_load.emplace(kTrialName, expiry,
blink::TrialToken::UsageRestriction::kNone,
kTrialSignature, partition_sites);
base::flat_set<PersistedTrialToken> all_tokens;
all_tokens.insert(tokens_in_db.begin(), tokens_in_db.end());
all_tokens.insert(tokens_before_load.begin(), tokens_before_load.end());
CreatePersistenceProvider();
// Use the persistence provider before DB is ready, and check that it behaves
// as expected
base::flat_set<PersistedTrialToken> stored_tokens =
persistence_provider_->GetPersistentTrialTokens(origin_b);
EXPECT_TRUE(stored_tokens.empty())
<< "No tokens should be available before the DB has loaded";
persistence_provider_->SavePersistentTrialTokens(origin_b,
tokens_before_load);
stored_tokens = persistence_provider_->GetPersistentTrialTokens(origin_b);
EXPECT_EQ(tokens_before_load, stored_tokens)
<< "The in-memory map should ensure synchronous operation, even before "
"DB load";
// Finish loading the DB
InitLevelDb(true);
FlushLoadCallback(true);
// One item should have been loaded
ht.ExpectUniqueSample("OriginTrials.PersistentOriginTrial.LevelDbLoadSize", 1,
1);
ht.ExpectUniqueSample(
"OriginTrials.PersistentOriginTrial.OriginsAddedBeforeDbLoad", 1, 1);
ht.ExpectUniqueSample(
"OriginTrials.PersistentOriginTrial.OriginLookupsBeforeDbLoad", 2, 1);
EXPECT_EQ(tokens_in_db,
persistence_provider_->GetPersistentTrialTokens(origin_a));
EXPECT_EQ(tokens_before_load,
persistence_provider_->GetPersistentTrialTokens(origin_b));
}
TEST_F(LevelDbPersistenceProviderUnitTest,
LoadFromDbDoesNotOverwriteInMemoryData) {
base::HistogramTester ht;
url::Origin db_origin = url::Origin::Create(GURL(kExampleComOrigin));
base::flat_set<std::string> partition_sites = {kExampleComDomain};
base::Time expiry = base::Time::Now() + base::Days(365);
base::flat_set<PersistedTrialToken> db_tokens;
db_tokens.emplace(kTrialName, expiry,
blink::TrialToken::UsageRestriction::kNone, kTrialSignature,
partition_sites);
db_entries_[db_origin.Serialize()] =
origin_trials_pb::ProtoFromTokens(db_origin, db_tokens);
CreatePersistenceProvider();
// The website used a new token, which should be saved
base::flat_set<PersistedTrialToken> live_tokens;
url::Origin live_origin =
url::Origin::Create(GURL(kSecondaryExampleComOrigin));
live_tokens.emplace(kTrialName, expiry,
blink::TrialToken::UsageRestriction::kNone,
kTrialSignatureAlternate, partition_sites);
persistence_provider_->SavePersistentTrialTokens(live_origin, live_tokens);
// Finish loading the DB
InitLevelDb(true);
FlushLoadCallback(true);
// Process any queued update operations
FlushUpdateCallback();
// One item should have been loaded
ht.ExpectUniqueSample("OriginTrials.PersistentOriginTrial.LevelDbLoadSize", 1,
1);
ht.ExpectUniqueSample(
"OriginTrials.PersistentOriginTrial.OriginsAddedBeforeDbLoad", 1, 1);
ht.ExpectUniqueSample(
"OriginTrials.PersistentOriginTrial.OriginLookupsBeforeDbLoad", 0, 1);
// After the load, we still expect to see the new token
base::flat_set<PersistedTrialToken> stored_tokens =
persistence_provider_->GetPersistentTrialTokens(live_origin);
EXPECT_EQ(live_tokens, stored_tokens);
// Check that the DB is updated with the new value as well after update
EXPECT_FALSE(db_entries_.empty());
auto lookup = db_entries_.find(live_origin.Serialize());
ASSERT_NE(db_entries_.end(), lookup);
auto db_token_array = lookup->second.tokens();
ASSERT_EQ(1, db_token_array.size());
origin_trials_pb::TrialTokenDbEntry& entry = *(db_token_array.begin());
EXPECT_EQ(kTrialSignatureAlternate, entry.token_signature());
}
TEST_F(LevelDbPersistenceProviderUnitTest,
WriteBeforeLoadShouldMergePartitions) {
url::Origin origin = url::Origin::Create(GURL(kExampleComOrigin));
base::flat_set<std::string> db_partitions = {kExampleComDomain};
base::Time expiry = base::Time::Now() + base::Days(365);
// The database has a token stored in a single partition |kExampleComDomain|.
base::flat_set<PersistedTrialToken> db_tokens;
db_tokens.emplace(kTrialName, expiry,
blink::TrialToken::UsageRestriction::kNone, kTrialSignature,
db_partitions);
db_entries_[origin.Serialize()] =
origin_trials_pb::ProtoFromTokens(origin, db_tokens);
CreatePersistenceProvider();
// Persist the same token in a new partition |kGoogleComDomain| before the
// database has loaded.
base::flat_set<std::string> live_partitions = {kGoogleComDomain};
base::flat_set<PersistedTrialToken> live_tokens;
live_tokens.emplace(kTrialName, expiry,
blink::TrialToken::UsageRestriction::kNone,
kTrialSignature, live_partitions);
persistence_provider_->SavePersistentTrialTokens(origin, live_tokens);
// Finish loading the DB
InitLevelDb(true);
FlushLoadCallback(true);
// Process any queued update operations
FlushUpdateCallback();
// We expect to see both the stored and the newly persisted partitions when
// we read from the persistence provider.
base::flat_set<PersistedTrialToken> stored_tokens =
persistence_provider_->GetPersistentTrialTokens(origin);
EXPECT_EQ(1UL, stored_tokens.size());
base::flat_set<std::string> expected_partitions = {kExampleComDomain,
kGoogleComDomain};
EXPECT_EQ(expected_partitions, stored_tokens.begin()->partition_sites);
// Check that the DB is updated with the new value as well.
EXPECT_FALSE(db_entries_.empty());
auto lookup = db_entries_.find(origin.Serialize());
ASSERT_NE(db_entries_.end(), lookup);
auto db_token_array = lookup->second.tokens();
ASSERT_EQ(1, db_token_array.size());
origin_trials_pb::TrialTokenDbEntry& entry = *(db_token_array.begin());
base::flat_set<std::string> saved_partitions(entry.partition_sites().begin(),
entry.partition_sites().end());
EXPECT_EQ(expected_partitions, saved_partitions);
}
} // namespace
} // namespace origin_trials