// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/webid/delegation/email_verifier_impl.h"

#include <optional>
#include <vector>

#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/test_future.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/webid/delegation/email_verification_request.h"
#include "content/public/browser/webid/email_verifier.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_renderer_host.h"
#include "net/base/schemeful_site.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace content::webid {

using ::testing::_;
using ::testing::ByMove;
using ::testing::NiceMock;
using ::testing::Optional;
using ::testing::Return;
using ::testing::StrictMock;
using ::testing::WithArgs;

class MockEmailVerificationRequest : public EmailVerificationRequest {
 public:
  explicit MockEmailVerificationRequest(RenderFrameHost& rfh)
      : EmailVerificationRequest(nullptr,
                                 nullptr,
                                 nullptr,
                                 static_cast<RenderFrameHostImpl&>(rfh)) {}
  ~MockEmailVerificationRequest() override { Destroy(); }

  MOCK_METHOD(void, Destroy, (), ());
  MOCK_METHOD(void,
              Send,
              (const std::string&,
               const std::string&,
               EmailVerifier::OnEmailVerifiedCallback));
  MOCK_METHOD(void,
              CheckIfVerifiable,
              (const std::string&,
               base::OnceClosure,
               EmailVerifier::IsVerifiableCallback));
  MOCK_METHOD(void,
              Verify,
              (const EmailVerifier::Result&,
               const std::string&,
               EmailVerifier::OnEmailVerifiedCallback),
              (override));
};

class MockRequestBuilder {
 public:
  MOCK_METHOD(std::unique_ptr<EmailVerificationRequest>, Run, ());
};

class EmailVerifierImplTest : public RenderViewHostTestHarness {
 public:
  EmailVerifierImplTest() = default;
  ~EmailVerifierImplTest() override = default;
};

TEST_F(EmailVerifierImplTest, TestSingleRequest) {
  auto request_is_verifiable =
      std::make_unique<StrictMock<MockEmailVerificationRequest>>(*main_rfh());
  auto* request_ptr_is_verifiable = request_is_verifiable.get();
  EXPECT_CALL(*request_ptr_is_verifiable, Destroy());

  auto request_verify =
      std::make_unique<StrictMock<MockEmailVerificationRequest>>(*main_rfh());
  auto* request_ptr_verify = request_verify.get();
  EXPECT_CALL(*request_ptr_verify, Destroy());

  MockRequestBuilder builder;
  EXPECT_CALL(builder, Run)
      .WillOnce(Return(ByMove(std::move(request_is_verifiable))))
      .WillOnce(Return(ByMove(std::move(request_verify))));

  EmailVerifierImpl verifier(base::BindRepeating(&MockRequestBuilder::Run,
                                                 base::Unretained(&builder)));

  EmailVerifier::Result issuer;
  issuer.email = "test@example.com";
  issuer.issuer_site = net::SchemefulSite(GURL("https://example.com"));

  base::test::TestFuture<std::optional<EmailVerifier::Result>,
                         blink::mojom::EmailVerificationRequestResult,
                         base::TimeDelta>
      verifiable_cb;

  EXPECT_CALL(*request_ptr_is_verifiable,
              CheckIfVerifiable("test@example.com", _, _))
      .WillOnce(WithArgs<2>([&](EmailVerifier::IsVerifiableCallback callback) {
        std::move(callback).Run(
            issuer, blink::mojom::EmailVerificationRequestResult::kSuccess,
            base::Milliseconds(100));
      }));

  verifier.CheckIfVerifiable("test@example.com", base::DoNothing(),
                             verifiable_cb.GetCallback());

  EXPECT_EQ(verifiable_cb.Get<0>(), issuer);

  EXPECT_CALL(*request_ptr_verify, Verify(_, "nonce", _))
      .WillOnce(
          WithArgs<2>([&](EmailVerifier::OnEmailVerifiedCallback callback) {
            std::move(callback).Run(
                "token", blink::mojom::EmailVerificationRequestResult::kSuccess,
                base::Milliseconds(200));
          }));

  base::MockCallback<EmailVerifier::OnEmailVerifiedCallback> cb;
  EXPECT_CALL(cb,
              Run(Optional(std::string("token")),
                  blink::mojom::EmailVerificationRequestResult::kSuccess, _));
  verifier.Verify(issuer, "nonce", cb.Get());
}

TEST_F(EmailVerifierImplTest, TestStatefulFlow) {
  auto request_is_verifiable =
      std::make_unique<StrictMock<MockEmailVerificationRequest>>(*main_rfh());
  auto* request_ptr_is_verifiable = request_is_verifiable.get();
  EXPECT_CALL(*request_ptr_is_verifiable, Destroy());

  auto request_verify =
      std::make_unique<StrictMock<MockEmailVerificationRequest>>(*main_rfh());
  auto* request_ptr_verify = request_verify.get();
  EXPECT_CALL(*request_ptr_verify, Destroy());

  MockRequestBuilder builder;
  EXPECT_CALL(builder, Run)
      .WillOnce(Return(ByMove(std::move(request_is_verifiable))))
      .WillOnce(Return(ByMove(std::move(request_verify))));

  EmailVerifierImpl verifier(base::BindRepeating(&MockRequestBuilder::Run,
                                                 base::Unretained(&builder)));

  const std::string kEmail = "test@example.com";
  const GURL kIssuanceEndpoint = GURL("https://issuer.example.com/token");

  EmailVerifier::Result issuer;
  issuer.email = kEmail;
  issuer.issuer_site = net::SchemefulSite(GURL("https://example.com"));
  issuer.issuance_endpoint = kIssuanceEndpoint;

  EXPECT_CALL(*request_ptr_is_verifiable, CheckIfVerifiable(kEmail, _, _))
      .WillOnce(WithArgs<2>([&](EmailVerifier::IsVerifiableCallback callback) {
        std::move(callback).Run(
            issuer, blink::mojom::EmailVerificationRequestResult::kSuccess,
            base::Milliseconds(100));
      }));

  base::test::TestFuture<std::optional<EmailVerifier::Result>,
                         blink::mojom::EmailVerificationRequestResult,
                         base::TimeDelta>
      verifiable_cb;
  verifier.CheckIfVerifiable(kEmail, base::DoNothing(),
                             verifiable_cb.GetCallback());

  EXPECT_EQ(verifiable_cb.Get<0>(), issuer);

  EXPECT_CALL(*request_ptr_verify, Verify(_, "nonce", _))
      .WillOnce(
          WithArgs<2>([&](EmailVerifier::OnEmailVerifiedCallback callback) {
            std::move(callback).Run(
                "token", blink::mojom::EmailVerificationRequestResult::kSuccess,
                base::Milliseconds(200));
          }));

  base::MockCallback<EmailVerifier::OnEmailVerifiedCallback> verified_cb;
  EXPECT_CALL(verified_cb,
              Run(Optional(std::string("token")),
                  blink::mojom::EmailVerificationRequestResult::kSuccess, _));
  verifier.Verify(issuer, "nonce", verified_cb.Get());
}

TEST_F(EmailVerifierImplTest, TestTwoConcurrentRequests) {
  auto request_is_verifiable1 =
      std::make_unique<StrictMock<MockEmailVerificationRequest>>(*main_rfh());
  auto* request_ptr_is_verifiable1 = request_is_verifiable1.get();
  EXPECT_CALL(*request_ptr_is_verifiable1, Destroy());

  auto request_is_verifiable2 =
      std::make_unique<StrictMock<MockEmailVerificationRequest>>(*main_rfh());
  auto* request_ptr_is_verifiable2 = request_is_verifiable2.get();
  EXPECT_CALL(*request_ptr_is_verifiable2, Destroy());

  auto request_verify1 =
      std::make_unique<StrictMock<MockEmailVerificationRequest>>(*main_rfh());
  auto* request_ptr_verify1 = request_verify1.get();
  EXPECT_CALL(*request_ptr_verify1, Destroy());

  auto request_verify2 =
      std::make_unique<StrictMock<MockEmailVerificationRequest>>(*main_rfh());
  auto* request_ptr_verify2 = request_verify2.get();
  EXPECT_CALL(*request_ptr_verify2, Destroy());

  MockRequestBuilder builder;
  EXPECT_CALL(builder, Run)
      .WillOnce(Return(ByMove(std::move(request_is_verifiable1))))
      .WillOnce(Return(ByMove(std::move(request_is_verifiable2))))
      .WillOnce(Return(ByMove(std::move(request_verify1))))
      .WillOnce(Return(ByMove(std::move(request_verify2))));

  EmailVerifierImpl verifier(base::BindRepeating(&MockRequestBuilder::Run,
                                                 base::Unretained(&builder)));

  EmailVerifier::Result issuer1;
  issuer1.email = "test1@example.com";
  issuer1.issuer_site = net::SchemefulSite(GURL("https://example.com"));

  EmailVerifier::Result issuer2;
  issuer2.email = "test2@example.com";
  issuer2.issuer_site = net::SchemefulSite(GURL("https://example.com"));

  // Set up expectations and capture callbacks for the two requests.
  EXPECT_CALL(*request_ptr_is_verifiable1,
              CheckIfVerifiable("test1@example.com", _, _))
      .WillOnce(WithArgs<2>([&](EmailVerifier::IsVerifiableCallback callback) {
        std::move(callback).Run(
            issuer1, blink::mojom::EmailVerificationRequestResult::kSuccess,
            base::Milliseconds(100));
      }));

  EXPECT_CALL(*request_ptr_is_verifiable2,
              CheckIfVerifiable("test2@example.com", _, _))
      .WillOnce(WithArgs<2>([&](EmailVerifier::IsVerifiableCallback callback) {
        std::move(callback).Run(
            issuer2, blink::mojom::EmailVerificationRequestResult::kSuccess,
            base::Milliseconds(100));
      }));

  base::test::TestFuture<std::optional<EmailVerifier::Result>,
                         blink::mojom::EmailVerificationRequestResult,
                         base::TimeDelta>
      verifiable_cb1;
  verifier.CheckIfVerifiable("test1@example.com", base::DoNothing(),
                             verifiable_cb1.GetCallback());

  base::test::TestFuture<std::optional<EmailVerifier::Result>,
                         blink::mojom::EmailVerificationRequestResult,
                         base::TimeDelta>
      verifiable_cb2;
  verifier.CheckIfVerifiable("test2@example.com", base::DoNothing(),
                             verifiable_cb2.GetCallback());

  EXPECT_EQ(verifiable_cb1.Get<0>(), issuer1);
  EXPECT_EQ(verifiable_cb2.Get<0>(), issuer2);

  // Now we need to expect the Verify calls!
  EmailVerifier::OnEmailVerifiedCallback callback1;
  EXPECT_CALL(*request_ptr_verify1, Verify(_, "nonce1", _))
      .WillOnce(
          WithArgs<2>([&](EmailVerifier::OnEmailVerifiedCallback callback) {
            callback1 = std::move(callback);
          }));

  EmailVerifier::OnEmailVerifiedCallback callback2;
  EXPECT_CALL(*request_ptr_verify2, Verify(_, "nonce2", _))
      .WillOnce(
          WithArgs<2>([&](EmailVerifier::OnEmailVerifiedCallback callback) {
            callback2 = std::move(callback);
          }));

  base::MockCallback<EmailVerifier::OnEmailVerifiedCallback> cb1;
  EXPECT_CALL(cb1,
              Run(Optional(std::string("token1")),
                  blink::mojom::EmailVerificationRequestResult::kSuccess, _));

  base::MockCallback<EmailVerifier::OnEmailVerifiedCallback> cb2;
  EXPECT_CALL(cb2,
              Run(Optional(std::string("token2")),
                  blink::mojom::EmailVerificationRequestResult::kSuccess, _));

  // Make the concurrent calls to Verify.
  verifier.Verify(issuer1, "nonce1", cb1.Get());
  verifier.Verify(issuer2, "nonce2", cb2.Get());

  ASSERT_TRUE(callback1);
  ASSERT_TRUE(callback2);

  // Complete in reverse order to test concurrency.
  std::move(callback2).Run(
      "token2", blink::mojom::EmailVerificationRequestResult::kSuccess,
      base::Milliseconds(200));
  std::move(callback1).Run(
      "token1", blink::mojom::EmailVerificationRequestResult::kSuccess,
      base::Milliseconds(200));
}

TEST_F(EmailVerifierImplTest, ForwardsDurationAndStatus) {
  std::unique_ptr<StrictMock<MockEmailVerificationRequest>>
      request_is_verifiable =
          std::make_unique<StrictMock<MockEmailVerificationRequest>>(
              *main_rfh());
  StrictMock<MockEmailVerificationRequest>* request_ptr_is_verifiable =
      request_is_verifiable.get();
  EXPECT_CALL(*request_ptr_is_verifiable, Destroy());

  std::unique_ptr<StrictMock<MockEmailVerificationRequest>> request_verify =
      std::make_unique<StrictMock<MockEmailVerificationRequest>>(*main_rfh());
  StrictMock<MockEmailVerificationRequest>* request_ptr_verify =
      request_verify.get();
  EXPECT_CALL(*request_ptr_verify, Destroy());

  MockRequestBuilder builder;
  EXPECT_CALL(builder, Run)
      .WillOnce(Return(ByMove(std::move(request_is_verifiable))))
      .WillOnce(Return(ByMove(std::move(request_verify))));

  EmailVerifierImpl verifier(base::BindRepeating(&MockRequestBuilder::Run,
                                                 base::Unretained(&builder)));

  const std::string kEmail = "test@example.com";
  const GURL kIssuanceEndpoint = GURL("https://issuer.example.com/token");

  EmailVerifier::Result issuer;
  issuer.email = kEmail;
  issuer.issuer_site = net::SchemefulSite(GURL("https://example.com"));
  issuer.issuance_endpoint = kIssuanceEndpoint;

  EXPECT_CALL(*request_ptr_is_verifiable, CheckIfVerifiable(kEmail, _, _))
      .WillOnce(WithArgs<2>([&](EmailVerifier::IsVerifiableCallback callback) {
        std::move(callback).Run(
            issuer, blink::mojom::EmailVerificationRequestResult::kSuccess,
            base::Milliseconds(100));
      }));

  base::test::TestFuture<std::optional<EmailVerifier::Result>,
                         blink::mojom::EmailVerificationRequestResult,
                         base::TimeDelta>
      verifiable_cb;
  verifier.CheckIfVerifiable(kEmail, base::DoNothing(),
                             verifiable_cb.GetCallback());
  EXPECT_EQ(verifiable_cb.Get<0>(), issuer);
  EXPECT_EQ(verifiable_cb.Get<1>(),
            blink::mojom::EmailVerificationRequestResult::kSuccess);
  EXPECT_EQ(verifiable_cb.Get<2>(), base::Milliseconds(100));

  EXPECT_CALL(*request_ptr_verify, Verify(_, "nonce", _))
      .WillOnce(
          WithArgs<2>([&](EmailVerifier::OnEmailVerifiedCallback callback) {
            std::move(callback).Run(
                "token", blink::mojom::EmailVerificationRequestResult::kSuccess,
                base::Milliseconds(200));
          }));

  base::MockCallback<EmailVerifier::OnEmailVerifiedCallback> verified_cb;
  EXPECT_CALL(verified_cb,
              Run(Optional(std::string("token")),
                  blink::mojom::EmailVerificationRequestResult::kSuccess,
                  base::Milliseconds(200)));
  verifier.Verify(issuer, "nonce", verified_cb.Get());
}

// This is a regression test for https://crbug.com/533251262.
TEST_F(EmailVerifierImplTest, ObserverUAFCrash) {
  auto request =
      std::make_unique<NiceMock<MockEmailVerificationRequest>>(*main_rfh());
  MockEmailVerificationRequest* request_ptr = request.get();

  MockRequestBuilder builder;
  EXPECT_CALL(builder, Run).WillOnce(Return(ByMove(std::move(request))));

  std::optional<EmailVerifierImpl> verifier;
  verifier.emplace(base::BindRepeating(&MockRequestBuilder::Run,
                                       base::Unretained(&builder)));

  EmailVerifier::IsVerifiableCallback captured_cb;
  EXPECT_CALL(*request_ptr, CheckIfVerifiable("test@example.com", _, _))
      .WillOnce(WithArgs<2>([&](EmailVerifier::IsVerifiableCallback cb) {
        captured_cb = std::move(cb);
      }));

  verifier->CheckIfVerifiable("test@example.com", base::DoNothing(),
                              base::DoNothing());

  // Destroy EmailVerifierImpl while the request is pending.
  verifier.reset();

  // Completing the request after EmailVerifierImpl was destroyed does not crash
  // because the observer is owned per-request by the callback.
  std::move(captured_cb)
      .Run(std::nullopt,
           blink::mojom::EmailVerificationRequestResult::kUserLoggedOut,
           base::Milliseconds(100));
}

// Verifies that EmailVerifierImpl::CheckIfVerifiable forwards
// on_dns_resolved_callback to EmailVerificationRequest.
TEST_F(EmailVerifierImplTest, ForwardsDnsResolvedCallback) {
  auto request =
      std::make_unique<NiceMock<MockEmailVerificationRequest>>(*main_rfh());
  MockEmailVerificationRequest* request_ptr = request.get();

  MockRequestBuilder builder;
  EXPECT_CALL(builder, Run).WillOnce(Return(ByMove(std::move(request))));

  EmailVerifierImpl verifier(base::BindRepeating(&MockRequestBuilder::Run,
                                                 base::Unretained(&builder)));

  base::MockCallback<base::OnceClosure> dns_resolved_cb;
  EXPECT_CALL(*request_ptr, CheckIfVerifiable("test@example.com", _, _))
      .WillOnce(
          WithArgs<1>([&](base::OnceClosure cb) { std::move(cb).Run(); }));

  EXPECT_CALL(dns_resolved_cb, Run()).Times(1);

  verifier.CheckIfVerifiable("test@example.com", dns_resolved_cb.Get(),
                             base::DoNothing());
}

}  // namespace content::webid
