// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/web/web_state/policy_decision_state_tracker.h"
#import <optional>
#import "base/functional/callback.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/platform_test.h"
namespace web {
class PolicyDecisionStateTrackerTest : public PlatformTest {
public:
PolicyDecisionStateTrackerTest()
: policy_decision_state_tracker_(base::BindOnce(
&PolicyDecisionStateTrackerTest::OnDecisionDetermined,
base::Unretained(this))) {}
void OnDecisionDetermined(
WebStatePolicyDecider::PolicyDecision policy_decision) {
policy_decision_ = policy_decision;
}
PolicyDecisionStateTracker policy_decision_state_tracker_;
std::optional<WebStatePolicyDecider::PolicyDecision> policy_decision_;
};
// Tests the case where every decision received is to allow the navigation, and
// each such decision is received before calling FinishedRequestingDecisions.
TEST_F(PolicyDecisionStateTrackerTest, AllAllowSync) {
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::Allow());
EXPECT_FALSE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_FALSE(policy_decision_);
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::Allow());
EXPECT_FALSE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_FALSE(policy_decision_);
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::Allow());
EXPECT_FALSE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_FALSE(policy_decision_);
int num_decisions_requested = 3;
policy_decision_state_tracker_.FinishedRequestingDecisions(
num_decisions_requested);
EXPECT_TRUE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_TRUE(policy_decision_);
EXPECT_TRUE(policy_decision_->ShouldAllowNavigation());
}
// Tests the case where every decision received is to allow the navigation, and
// each such decision is received after calling FinishedRequestingDecisions.
TEST_F(PolicyDecisionStateTrackerTest, AllAllowAsync) {
int num_decisions_requested = 3;
policy_decision_state_tracker_.FinishedRequestingDecisions(
num_decisions_requested);
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::Allow());
EXPECT_FALSE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_FALSE(policy_decision_);
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::Allow());
EXPECT_FALSE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_FALSE(policy_decision_);
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::Allow());
EXPECT_TRUE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_TRUE(policy_decision_);
EXPECT_TRUE(policy_decision_->ShouldAllowNavigation());
}
// Tests the case where every decision received is to allow the navigation, and
// some decisions are received before calling FinishedRequestingDecisions while
// the rest of the decisions are received later.
TEST_F(PolicyDecisionStateTrackerTest, AllAllowMixed) {
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::Allow());
EXPECT_FALSE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_FALSE(policy_decision_);
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::Allow());
EXPECT_FALSE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_FALSE(policy_decision_);
int num_decisions_requested = 4;
policy_decision_state_tracker_.FinishedRequestingDecisions(
num_decisions_requested);
EXPECT_FALSE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_FALSE(policy_decision_);
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::Allow());
EXPECT_FALSE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_FALSE(policy_decision_);
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::Allow());
EXPECT_TRUE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_TRUE(policy_decision_);
EXPECT_TRUE(policy_decision_->ShouldAllowNavigation());
}
// Tests the case where a decision to cancel the navigation is received before
// FinishedRequestingDecisions is called.
TEST_F(PolicyDecisionStateTrackerTest, CancelSync) {
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::Allow());
EXPECT_FALSE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_FALSE(policy_decision_);
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::Cancel());
EXPECT_TRUE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_TRUE(policy_decision_);
EXPECT_TRUE(policy_decision_->ShouldCancelNavigation());
EXPECT_FALSE(policy_decision_->ShouldDisplayError());
// Verify that additional calls into `policy_decision_state_tracker_` do not
// lead to additional calls to its callback, which would crash since the
// callback is a OnceCallback.
int num_decisions_requested = 4;
policy_decision_state_tracker_.FinishedRequestingDecisions(
num_decisions_requested);
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::Allow());
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::Cancel());
}
// Tests the case where a decision to cancel the navigation is received after
// FinishedRequestingDecisions is called.
TEST_F(PolicyDecisionStateTrackerTest, CancelAsync) {
NSError* error = [NSError errorWithDomain:@"ErrorDomain" code:1 userInfo:nil];
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::CancelAndDisplayError(error));
EXPECT_FALSE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_FALSE(policy_decision_);
int num_decisions_requested = 4;
policy_decision_state_tracker_.FinishedRequestingDecisions(
num_decisions_requested);
EXPECT_FALSE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_FALSE(policy_decision_);
// A decision to cancel without an error should take precedence over the
// decision to show an error.
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::Cancel());
EXPECT_TRUE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_TRUE(policy_decision_);
EXPECT_TRUE(policy_decision_->ShouldCancelNavigation());
EXPECT_FALSE(policy_decision_->ShouldDisplayError());
// Verify that an additional calls into policy_decision_state_tracker_ do not
// lead to additional calls to its callback, which would crash since the
// callback is a OnceCallback.
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::Allow());
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::Cancel());
}
// Tests the case where a decision to show an error is received before
// FinishedRequestingDecisions is called.
TEST_F(PolicyDecisionStateTrackerTest, ShowErrorSync) {
NSError* error = [NSError errorWithDomain:@"ErrorDomain" code:1 userInfo:nil];
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::CancelAndDisplayError(error));
EXPECT_FALSE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_FALSE(policy_decision_);
int num_decisions_requested = 3;
policy_decision_state_tracker_.FinishedRequestingDecisions(
num_decisions_requested);
EXPECT_FALSE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_FALSE(policy_decision_);
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::Allow());
EXPECT_FALSE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_FALSE(policy_decision_);
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::Allow());
EXPECT_TRUE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_TRUE(policy_decision_);
EXPECT_TRUE(policy_decision_->ShouldCancelNavigation());
EXPECT_TRUE(policy_decision_->ShouldDisplayError());
}
// Tests the case where decisions to show an error are received after
// FinishedRequestingDecisions is called.
TEST_F(PolicyDecisionStateTrackerTest, ShowErrorAsync) {
int num_decisions_requested = 3;
policy_decision_state_tracker_.FinishedRequestingDecisions(
num_decisions_requested);
EXPECT_FALSE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_FALSE(policy_decision_);
NSError* error1 = [NSError errorWithDomain:@"ErrorDomain"
code:1
userInfo:nil];
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::CancelAndDisplayError(error1));
EXPECT_FALSE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_FALSE(policy_decision_);
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::Allow());
EXPECT_FALSE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_FALSE(policy_decision_);
NSError* error2 = [NSError errorWithDomain:@"ErrorDomain"
code:2
userInfo:nil];
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::CancelAndDisplayError(error2));
EXPECT_TRUE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_TRUE(policy_decision_);
EXPECT_TRUE(policy_decision_->ShouldCancelNavigation());
EXPECT_TRUE(policy_decision_->ShouldDisplayError());
// The error received first should take precedence.
EXPECT_EQ(policy_decision_->GetDisplayError().code, error1.code);
}
// Tests the case where decisions to show an error are received both before and
// after FinishedRequestingDecisions is called.
TEST_F(PolicyDecisionStateTrackerTest, ShowErrorMixed) {
NSError* error1 = [NSError errorWithDomain:@"ErrorDomain"
code:1
userInfo:nil];
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::CancelAndDisplayError(error1));
EXPECT_FALSE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_FALSE(policy_decision_);
int num_decisions_requested = 3;
policy_decision_state_tracker_.FinishedRequestingDecisions(
num_decisions_requested);
EXPECT_FALSE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_FALSE(policy_decision_);
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::Allow());
EXPECT_FALSE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_FALSE(policy_decision_);
NSError* error2 = [NSError errorWithDomain:@"ErrorDomain"
code:2
userInfo:nil];
policy_decision_state_tracker_.OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::CancelAndDisplayError(error2));
EXPECT_TRUE(policy_decision_state_tracker_.DeterminedFinalResult());
EXPECT_TRUE(policy_decision_);
EXPECT_TRUE(policy_decision_->ShouldCancelNavigation());
EXPECT_TRUE(policy_decision_->ShouldDisplayError());
// The error received first should take precedence.
EXPECT_EQ(policy_decision_->GetDisplayError().code, error1.code);
}
// Test fixture that supports destroying its PolicyDecisionStateTracker,
// allowing destructor behavior to be tested.
class PolicyDecisionStateTrackerDestructionTest : public PlatformTest {
public:
PolicyDecisionStateTrackerDestructionTest()
: policy_decision_state_tracker_(
std::make_unique<PolicyDecisionStateTracker>(
base::BindOnce(&PolicyDecisionStateTrackerDestructionTest::
OnDecisionDetermined,
base::Unretained(this)))) {}
void OnDecisionDetermined(
WebStatePolicyDecider::PolicyDecision policy_decision) {
policy_decision_ = policy_decision;
}
void DestroyPolicyDecisionStateTracker() {
policy_decision_state_tracker_.reset();
}
std::unique_ptr<PolicyDecisionStateTracker> policy_decision_state_tracker_;
std::optional<WebStatePolicyDecider::PolicyDecision> policy_decision_;
};
// Tests the case where no decisions have been received by the time the
// PolicyDecisionStateTracker is destroyed.
TEST_F(PolicyDecisionStateTrackerDestructionTest, NoDecisionsReceived) {
int num_decisions_requested = 3;
policy_decision_state_tracker_->FinishedRequestingDecisions(
num_decisions_requested);
DestroyPolicyDecisionStateTracker();
EXPECT_TRUE(policy_decision_);
EXPECT_TRUE(policy_decision_->ShouldCancelNavigation());
}
// Tests the case where some but not all decisions have been received by the
// time the PolicyDecisionStateTracker is destroyed.
TEST_F(PolicyDecisionStateTrackerDestructionTest, OnlySomeDecisionsReceived) {
int num_decisions_requested = 3;
policy_decision_state_tracker_->FinishedRequestingDecisions(
num_decisions_requested);
policy_decision_state_tracker_->OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::Allow());
EXPECT_FALSE(policy_decision_state_tracker_->DeterminedFinalResult());
EXPECT_FALSE(policy_decision_);
policy_decision_state_tracker_->OnSinglePolicyDecisionReceived(
WebStatePolicyDecider::PolicyDecision::Allow());
EXPECT_FALSE(policy_decision_state_tracker_->DeterminedFinalResult());
EXPECT_FALSE(policy_decision_);
DestroyPolicyDecisionStateTracker();
EXPECT_TRUE(policy_decision_);
EXPECT_TRUE(policy_decision_->ShouldCancelNavigation());
}
} // namespace web