chromium/chrome/browser/ash/printing/printer_authenticator_unittest.cc

// 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 "chrome/browser/ash/printing/printer_authenticator.h"

#include <memory>
#include <string>

#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/mock_callback.h"
#include "chrome/browser/ash/printing/fake_cups_printers_manager.h"
#include "chrome/browser/ash/printing/oauth2/mock_authorization_zones_manager.h"
#include "chrome/browser/ash/printing/oauth2/status_code.h"
#include "chromeos/printing/cups_printer_status.h"
#include "chromeos/printing/printer_configuration.h"
#include "chromeos/printing/uri.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace ash::printing {
namespace {

// Represents results returned by callback void(StatusCode, const string&).
struct CallbackResult {
  oauth2::StatusCode status = oauth2::StatusCode::kUnexpectedError;
  std::string data;
};

class PrintingPrinterAuthenticatorTest : public testing::Test {
 public:
  PrintingPrinterAuthenticatorTest() : printer_(kPrinterId) {}

 protected:
  // Init PrintAuthenticator object being tested. Values of parameters define
  // results returned by simulated UI dialogs.
  void CreateAuthenticator(oauth2::StatusCode is_trusted_dialog_response =
                               oauth2::StatusCode::kUnexpectedError,
                           oauth2::StatusCode signin_dialog_response =
                               oauth2::StatusCode::kUnexpectedError) {
    authenticator_ = std::make_unique<PrinterAuthenticator>(
        &cups_manager_, &auth_manager_, printer_);
    authenticator_->SetUIResponsesForTesting(is_trusted_dialog_response,
                                             signin_dialog_response);
  }

  // Call PrintAuthenticator::ObtainAccessTokenIfNeeded() and returns result
  // reported by the callback.
  CallbackResult CallObtainAccessTokenIfNeeded() {
    base::MockOnceCallback<void(oauth2::StatusCode, std::string)> callback;
    CallbackResult result;
    base::RunLoop loop;
    EXPECT_CALL(callback, Run)
        .InSequence(sequence_)
        .WillOnce(
            [&result, &loop](oauth2::StatusCode status, std::string data) {
              result.status = status;
              result.data = std::move(data);
              loop.Quit();
            });
    authenticator_->ObtainAccessTokenIfNeeded(callback.Get());
    loop.Run();
    return result;
  }

  // Mock CupsPrintersManager::FetchPrinterStatus. `auth_mode` is passed to the
  // object chromeos::CupsPrinterStatus returned in the callback.
  void ExpectCallFetchPrinterStatus(
      const chromeos::PrinterAuthenticationInfo& auth_mode) {
    chromeos::CupsPrinterStatus printer_status(kPrinterId);
    printer_status.SetAuthenticationInfo(auth_mode);
    cups_manager_.SetPrinterStatus(printer_status);
  }

  // Mock AuthorizationZoneManager::SaveAuthorizationServerAsTrusted. `result`
  // will be return by the call.
  void ExpectCallSaveAuthorizationServerAsTrusted(oauth2::StatusCode result) {
    EXPECT_CALL(auth_manager_, SaveAuthorizationServerAsTrusted(testing::_))
        .InSequence(sequence_)
        .WillOnce([result](const GURL& auth_server) { return result; });
  }

  // Mock AuthorizationZoneManager::InitAuthorization. Callback passed to the
  // mocked method will be called with parameters set in `result`.
  void ExpectCallInitAuthorization(CallbackResult result) {
    EXPECT_CALL(auth_manager_,
                InitAuthorization(testing::_, testing::_, testing::_))
        .InSequence(sequence_)
        .WillOnce([result](const GURL& auth_server, const std::string& scope,
                           oauth2::StatusCallback callback) {
          std::move(callback).Run(result.status, std::move(result.data));
        });
  }

  // Mock AuthorizationZoneManager::FinishAuthorization. Callback passed to the
  // mocked method will be called with parameters set in `result`.
  void ExpectCallFinishAuthorization(CallbackResult result) {
    EXPECT_CALL(auth_manager_,
                FinishAuthorization(testing::_, testing::_, testing::_))
        .InSequence(sequence_)
        .WillOnce([result](const GURL& auth_server, const GURL& redirect_url,
                           oauth2::StatusCallback callback) {
          std::move(callback).Run(result.status, std::move(result.data));
        });
  }

  // Mock AuthorizationZoneManager::GetEndpointAccessToken. Callback passed to
  // the mocked method will be called with parameters set in `result`.
  void ExpectCallGetEndpointAccessToken(CallbackResult result) {
    EXPECT_CALL(auth_manager_, GetEndpointAccessToken(testing::_, testing::_,
                                                      testing::_, testing::_))
        .InSequence(sequence_)
        .WillOnce([result](const GURL& auth_server,
                           const chromeos::Uri& ipp_endpoint,
                           const std::string& scope,
                           oauth2::StatusCallback callback) {
          std::move(callback).Run(result.status, std::move(result.data));
        });
  }

  const std::string kPrinterId = "printer_id";
  const chromeos::PrinterAuthenticationInfo kAuthMode = {"https://auth/server",
                                                         "scope1 scope2"};
  const std::string kAuthURL = "https://auth/server/login?ala=ma;kota";

 private:
  content::BrowserTaskEnvironment task_environment_;
  testing::Sequence sequence_;
  FakeCupsPrintersManager cups_manager_;
  testing::StrictMock<oauth2::MockAuthorizationZoneManager> auth_manager_;
  chromeos::Printer printer_;
  std::unique_ptr<PrinterAuthenticator> authenticator_;
};

TEST_F(PrintingPrinterAuthenticatorTest, AuthenticationNotNeeded) {
  CreateAuthenticator();
  ExpectCallFetchPrinterStatus({"", ""});
  CallbackResult result = CallObtainAccessTokenIfNeeded();
  EXPECT_EQ(result.status, oauth2::StatusCode::kOK);
  EXPECT_TRUE(result.data.empty());
}

TEST_F(PrintingPrinterAuthenticatorTest, AlreadyAuthenticated) {
  CreateAuthenticator(oauth2::StatusCode::kOK);
  ExpectCallFetchPrinterStatus(kAuthMode);
  ExpectCallGetEndpointAccessToken({oauth2::StatusCode::kOK, "access token"});
  CallbackResult result = CallObtainAccessTokenIfNeeded();
  EXPECT_EQ(result.status, oauth2::StatusCode::kOK);
  EXPECT_EQ(result.data, "access token");
}

TEST_F(PrintingPrinterAuthenticatorTest, ServerNotTrusted) {
  CreateAuthenticator(oauth2::StatusCode::kUntrustedAuthorizationServer);
  ExpectCallFetchPrinterStatus(kAuthMode);
  ExpectCallGetEndpointAccessToken(
      {oauth2::StatusCode::kUntrustedAuthorizationServer});
  // Response from IsTrustedDialog: StatusCode::kUntrustedAuthorizationServer
  CallbackResult result = CallObtainAccessTokenIfNeeded();
  EXPECT_EQ(result.status, oauth2::StatusCode::kUntrustedAuthorizationServer);
  EXPECT_TRUE(result.data.empty());
}

TEST_F(PrintingPrinterAuthenticatorTest, ServerSavedAsTrusted) {
  CreateAuthenticator(oauth2::StatusCode::kOK);
  ExpectCallFetchPrinterStatus(kAuthMode);
  ExpectCallGetEndpointAccessToken(
      {oauth2::StatusCode::kUntrustedAuthorizationServer});
  // Response from IsTrustedDialog: StatusCode::kOK
  ExpectCallSaveAuthorizationServerAsTrusted(oauth2::StatusCode::kOK);
  ExpectCallGetEndpointAccessToken(
      {oauth2::StatusCode::kClientNotRegistered, "error"});
  CallbackResult result = CallObtainAccessTokenIfNeeded();
  EXPECT_EQ(result.status, oauth2::StatusCode::kClientNotRegistered);
  EXPECT_TRUE(result.data.empty());
}

TEST_F(PrintingPrinterAuthenticatorTest, InvalidAuthorizationURL) {
  CreateAuthenticator();
  ExpectCallFetchPrinterStatus(kAuthMode);
  ExpectCallGetEndpointAccessToken({oauth2::StatusCode::kAuthorizationNeeded});
  ExpectCallInitAuthorization({oauth2::StatusCode::kOK, "invalid_auth_url"});
  // Response from SigninDialog: StatusCode::kInvalidURL
  CallbackResult result = CallObtainAccessTokenIfNeeded();
  EXPECT_EQ(result.status, oauth2::StatusCode::kInvalidURL);
  EXPECT_TRUE(result.data.empty());
}

TEST_F(PrintingPrinterAuthenticatorTest, AuthorizationError) {
  CreateAuthenticator(oauth2::StatusCode::kOK,
                      oauth2::StatusCode::kServerError);
  ExpectCallFetchPrinterStatus(kAuthMode);
  ExpectCallGetEndpointAccessToken({oauth2::StatusCode::kAuthorizationNeeded});
  ExpectCallInitAuthorization({oauth2::StatusCode::kOK, kAuthURL});
  // Response from SigninDialog: StatusCode::kServerError
  CallbackResult result = CallObtainAccessTokenIfNeeded();
  EXPECT_EQ(result.status, oauth2::StatusCode::kServerError);
  EXPECT_TRUE(result.data.empty());
}

TEST_F(PrintingPrinterAuthenticatorTest, AccessDenied) {
  CreateAuthenticator(oauth2::StatusCode::kOK, oauth2::StatusCode::kOK);
  ExpectCallFetchPrinterStatus(kAuthMode);
  ExpectCallGetEndpointAccessToken({oauth2::StatusCode::kAuthorizationNeeded});
  ExpectCallInitAuthorization({oauth2::StatusCode::kOK, kAuthURL});
  // Response from SigninDialog: StatusCode::kOK
  ExpectCallFinishAuthorization({oauth2::StatusCode::kAccessDenied, "error"});
  CallbackResult result = CallObtainAccessTokenIfNeeded();
  EXPECT_EQ(result.status, oauth2::StatusCode::kAccessDenied);
  EXPECT_TRUE(result.data.empty());
}

TEST_F(PrintingPrinterAuthenticatorTest, AuthorizationSuccessful) {
  CreateAuthenticator(oauth2::StatusCode::kOK, oauth2::StatusCode::kOK);
  ExpectCallFetchPrinterStatus(kAuthMode);
  ExpectCallGetEndpointAccessToken({oauth2::StatusCode::kAuthorizationNeeded});
  ExpectCallInitAuthorization({oauth2::StatusCode::kOK, kAuthURL});
  // Response from SigninDialog: StatusCode::kOK
  ExpectCallFinishAuthorization({oauth2::StatusCode::kOK});
  ExpectCallGetEndpointAccessToken({oauth2::StatusCode::kOK, "access token"});
  CallbackResult result = CallObtainAccessTokenIfNeeded();
  EXPECT_EQ(result.status, oauth2::StatusCode::kOK);
  EXPECT_EQ(result.data, "access token");
}

}  // namespace
}  // namespace ash::printing