chromium/chrome/browser/ash/printing/enterprise/print_servers_provider_unittest.cc

// Copyright 2019 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/enterprise/print_servers_provider.h"

#include <string>
#include <vector>

#include "chrome/browser/ash/printing/print_server.h"
#include "chrome/common/pref_names.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace ash {
namespace {

// An example of configuration file with print servers.
constexpr char kPrintServersPolicyJson1[] = R"json(
[
  {
    "id": "id1",
    "display_name": "MyPrintServer",
    "url": "ipp://192.168.1.5"
  }, {
    "id": "id2",
    "display_name": "Server API",
    "url":"ipps://print-server.intra.example.com:444/ipp/cl2k4"
  }, {
    "id": "id3",
    "display_name": "YaLP",
    "url": "http://192.168.1.8/bleble/print"
  }
])json";

PrintServer Server1() {
  return {"id1", GURL("http://192.168.1.5:631"), "MyPrintServer"};
}

PrintServer Server3() {
  return {"id3", GURL("http://192.168.1.8/bleble/print"), "YaLP"};
}

// Corresponding vector with PrintServers.
std::vector<PrintServer> PrintServersPolicyData1() {
  return {{Server1(),
           {"id2", GURL("https://print-server.intra.example.com:444/ipp/cl2k4"),
            "Server API"},
           Server3()}};
}

// Observer that stores all its calls.
class TestObserver : public PrintServersProvider::Observer {
 public:
  struct ObserverCall {
    bool complete;
    std::vector<PrintServer> servers;
    ObserverCall(bool complete, std::vector<PrintServer> servers)
        : complete(complete), servers(std::move(servers)) {}
  };

  ~TestObserver() override = default;

  // Callback from PrintServersProvider::Observer.
  void OnServersChanged(bool complete,
                        const std::vector<PrintServer>& servers) override {
    calls_.emplace_back(complete, servers);
  }

  // Returns history of all calls to OnServersChanged(...).
  const std::vector<ObserverCall>& GetCalls() const { return calls_; }

 private:
  // history of all callbacks
  std::vector<ObserverCall> calls_;
};

class PrintServersProviderTest : public testing::Test {
 public:
  PrintServersProviderTest() = default;

 protected:
  void SetUp() override {
    pref_service_.registry()->RegisterListPref(kAllowlistPrefName);
    external_servers_->SetAllowlistPref(&pref_service_, kAllowlistPrefName);
  }

  static constexpr char kAllowlistPrefName[] = "test";

  // Everything must be called on Chrome_UIThread.
  content::BrowserTaskEnvironment task_environment_;
  // Test prefs.
  sync_preferences::TestingPrefServiceSyncable pref_service_;
  // Tested object.
  std::unique_ptr<PrintServersProvider> external_servers_ =
      PrintServersProvider::Create();
};

// static
constexpr char PrintServersProviderTest::kAllowlistPrefName[];

// Verify that the object can be destroyed while parsing is in progress.
TEST_F(PrintServersProviderTest, DestructionIsSafe) {
  {
    std::unique_ptr<PrintServersProvider> servers =
        PrintServersProvider::Create();
    servers->SetData(std::make_unique<std::string>(kPrintServersPolicyJson1));
    // Data is valid.  Computation is proceeding.
  }
  // servers is out of scope.  Destructor has run.  Pump the message queue to
  // see if anything strange happens.
  task_environment_.RunUntilIdle();
}

// Verify that we're initially unset and empty.
// After initialization "complete" flags = false.
TEST_F(PrintServersProviderTest, InitialConditions) {
  TestObserver obs;
  external_servers_->AddObserver(&obs);
  ASSERT_EQ(obs.GetCalls().size(), 1u);
  EXPECT_EQ(obs.GetCalls().back().complete, false);
  EXPECT_TRUE(obs.GetCalls().back().servers.empty());
  external_servers_->RemoveObserver(&obs);
}

// Verify two ClearData() calls.
// ClearData() sets empty list and "complete" flag = true.
TEST_F(PrintServersProviderTest, ClearData2) {
  TestObserver obs;
  external_servers_->AddObserver(&obs);
  external_servers_->ClearData();
  ASSERT_EQ(obs.GetCalls().size(), 2u);
  EXPECT_EQ(obs.GetCalls().back().complete, true);
  EXPECT_TRUE(obs.GetCalls().back().servers.empty());
  external_servers_->ClearData();
  // no changes, because observed object's state is the same
  ASSERT_EQ(obs.GetCalls().size(), 2u);
  external_servers_->RemoveObserver(&obs);
}

// Verifies SetData().
// SetData(...) sets "complete" flag = false, then parse given data in the
// background and sets resultant list with "complete" flag = true.
TEST_F(PrintServersProviderTest, SetData) {
  TestObserver obs;
  external_servers_->AddObserver(&obs);
  external_servers_->SetData(
      std::make_unique<std::string>(kPrintServersPolicyJson1));
  // single call from AddObserver, since SetData(...) is not processed yet
  ASSERT_EQ(obs.GetCalls().size(), 1u);
  // make sure that SetData(...) is processed
  task_environment_.RunUntilIdle();
  // now the call from SetData(...) is there also
  ASSERT_EQ(obs.GetCalls().size(), 2u);
  EXPECT_EQ(obs.GetCalls().back().complete, true);
  EXPECT_EQ(obs.GetCalls().back().servers, PrintServersPolicyData1());
  external_servers_->RemoveObserver(&obs);
}

// Verify two SetData() calls.
TEST_F(PrintServersProviderTest, SetData2) {
  TestObserver obs;
  external_servers_->AddObserver(&obs);
  external_servers_->SetData(
      std::make_unique<std::string>(kPrintServersPolicyJson1));
  // single call from AddObserver, since SetData(...) is not processed yet
  ASSERT_EQ(obs.GetCalls().size(), 1u);
  external_servers_->SetData(std::make_unique<std::string>(R"json(
[
  {
    "id": "id1",
    "display_name": "CUPS",
    "url": "ipp://192.168.1.15"
  }
])json"));
  // no changes, because nothing was processed yet
  ASSERT_EQ(obs.GetCalls().size(), 1u);
  task_environment_.RunUntilIdle();
  // both calls from SetData(...) should be reported
  ASSERT_EQ(obs.GetCalls().size(), 3u);
  EXPECT_EQ(obs.GetCalls()[1].complete, false);
  EXPECT_EQ(obs.GetCalls()[1].servers, PrintServersPolicyData1());
  EXPECT_EQ(obs.GetCalls()[2].complete, true);
  EXPECT_EQ(obs.GetCalls()[2].servers,
            std::vector<PrintServer>(
                {{"id1", GURL("http://192.168.1.15:631"), "CUPS"}}));
  external_servers_->RemoveObserver(&obs);
}

// Verifies SetData() + ClearData() before SetData() completes.
TEST_F(PrintServersProviderTest, SetDataClearData) {
  TestObserver obs;
  external_servers_->AddObserver(&obs);
  external_servers_->SetData(
      std::make_unique<std::string>(kPrintServersPolicyJson1));
  // single call from AddObserver, since SetData(...) is not processed yet
  ASSERT_EQ(obs.GetCalls().size(), 1u);
  external_servers_->ClearData();
  // a call from ClearData() was added, SetData(...) is not processed yet
  ASSERT_EQ(obs.GetCalls().size(), 2u);
  EXPECT_EQ(obs.GetCalls().back().complete, true);
  EXPECT_TRUE(obs.GetCalls().back().servers.empty());
  // process SetData(...)
  task_environment_.RunUntilIdle();
  // no changes, effects of SetData(...) were already replaced by ClearData()
  ASSERT_EQ(obs.GetCalls().size(), 2u);
  external_servers_->RemoveObserver(&obs);
}

// Verifies ClearData() before AddObserver() + SetData() after.
TEST_F(PrintServersProviderTest, ClearDataSetData) {
  TestObserver obs;
  external_servers_->ClearData();
  external_servers_->AddObserver(&obs);
  // single call from AddObserver, but with effects of ClearData()
  ASSERT_EQ(obs.GetCalls().size(), 1u);
  EXPECT_EQ(obs.GetCalls().back().complete, true);
  EXPECT_TRUE(obs.GetCalls().back().servers.empty());
  external_servers_->SetData(
      std::make_unique<std::string>(kPrintServersPolicyJson1));
  // SetData(...) is not completed, but generates a call switching "complete"
  // flag to false
  ASSERT_EQ(obs.GetCalls().size(), 2u);
  EXPECT_EQ(obs.GetCalls().back().complete, false);
  EXPECT_TRUE(obs.GetCalls().back().servers.empty());
  // process SetData(...)
  task_environment_.RunUntilIdle();
  // next call with results from processed SetData(...)
  ASSERT_EQ(obs.GetCalls().size(), 3u);
  EXPECT_EQ(obs.GetCalls().back().complete, true);
  EXPECT_EQ(obs.GetCalls().back().servers, PrintServersPolicyData1());
  external_servers_->RemoveObserver(&obs);
}

// Verify that invalid URLs are filtered out.
TEST_F(PrintServersProviderTest, InvalidURLs) {
  TestObserver obs;
  external_servers_->AddObserver(&obs);
  external_servers_->SetData(std::make_unique<std::string>(R"json(
[
  {
    "id": "1",
    "display_name": "server_1",
    "url": "ipp://aaa.bbb.ccc:666/xx"
  }, {
    "id": "2",
    "display_name": "server_2",
    "url":"ipps:/print.server.intra.example.com:443z/ipp"
  }, {
    "id": "3",
    "display_name": "server_3",
    "url": "file://192.168.1.8/bleble/print"
  }, {
    "id": "4",
    "display_name": "server_4",
    "url": "http://aaa.bbb.ccc:666/xx"
  }, {
    "id": "5",
    "display_name": "server_5",
    "url": "\n \t ipps://aaa.bbb.ccc:666/yy"
  }, {
    "id": "6",
    "display_name": "server_6",
    "url":"ipps:/print.server^.intra.example.com/ipp"
  }, {
    "id": "3",
    "display_name": "server_7",
    "url": "file://194.169.2.18/bleble2/print"
  }, {
    "display_name": "server_8",
    "url": "file://195.161.3.28/bleble/print"
  }
])json"));
  task_environment_.RunUntilIdle();
  ASSERT_EQ(obs.GetCalls().size(), 2u);
  EXPECT_EQ(obs.GetCalls().back().complete, true);
  // Only two records are valid:
  // server_1 - OK
  // server_2 - invalid URL - invalid port number
  // server_3 - unsupported scheme
  // server_4 - duplicate of server_1
  // server_5 - leading whitespaces, but OK
  // server_6 - invalid URL - forbidden character
  // server_7 - duplicate id
  // server_8 - missing id
  EXPECT_EQ(obs.GetCalls().back().servers,
            std::vector<PrintServer>(
                {{"1", GURL("http://aaa.bbb.ccc:666/xx"), "server_1"},
                 {"5", GURL("https://aaa.bbb.ccc:666/yy"), "server_5"}}));
  external_servers_->RemoveObserver(&obs);
}

// Verify that allowlist works as expected.
TEST_F(PrintServersProviderTest, Allowlist) {
  // The sequence from SetData test.
  TestObserver obs;
  external_servers_->AddObserver(&obs);
  external_servers_->SetData(
      std::make_unique<std::string>(kPrintServersPolicyJson1));
  // Apply an empty allowlist on the top.
  pref_service_.SetManagedPref(kAllowlistPrefName,
                               base::Value(base::Value::Type::LIST));
  // Check the resultant list - is is supposed to be empty.
  task_environment_.RunUntilIdle();
  ASSERT_FALSE(obs.GetCalls().empty());
  EXPECT_TRUE(obs.GetCalls().back().complete);
  EXPECT_TRUE(obs.GetCalls().back().servers.empty());
  // Apply allowlist.
  base::Value::List value;
  for (std::string id : {"id3", "idX", "id1"})
    value.Append(std::move(id));
  pref_service_.SetManagedPref(kAllowlistPrefName,
                               base::Value(std::move(value)));
  // Check the resultant list.
  task_environment_.RunUntilIdle();
  EXPECT_TRUE(obs.GetCalls().back().complete);
  EXPECT_EQ(obs.GetCalls().back().servers,
            std::vector<PrintServer>({Server1(), Server3()}));
  // The end.
  external_servers_->RemoveObserver(&obs);
}

}  // namespace
}  // namespace ash