chromium/ios/web/web_state/policy_decision_state_tracker_unittest.mm

// 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