chromium/chrome/browser/ash/file_system_provider/fileapi/file_stream_reader_unittest.cc

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

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/browser/ash/file_system_provider/fileapi/file_stream_reader.h"

#include <stddef.h>
#include <stdint.h>

#include <memory>
#include <string>
#include <vector>

#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/numerics/safe_math.h"
#include "base/run_loop.h"
#include "chrome/browser/ash/file_system_provider/fake_extension_provider.h"
#include "chrome/browser/ash/file_system_provider/fake_provided_file_system.h"
#include "chrome/browser/ash/file_system_provider/service.h"
#include "chrome/browser/ash/file_system_provider/service_factory.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "content/public/test/browser_task_environment.h"
#include "extensions/browser/extension_registry.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "storage/browser/file_system/async_file_util.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "storage/browser/file_system/file_system_url.h"
#include "storage/browser/test/test_file_system_context.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"

namespace ash::file_system_provider {
namespace {

const char kExtensionId[] = "mbflcebpggnecokmikipoihdbecnjfoj";
const char kFileSystemId[] = "testing-file-system";
const ProviderId kProviderId = ProviderId::CreateFromExtensionId(kExtensionId);

// Logs callbacks invocations on the file stream reader.
class EventLogger {
 public:
  EventLogger() = default;

  EventLogger(const EventLogger&) = delete;
  EventLogger& operator=(const EventLogger&) = delete;

  virtual ~EventLogger() = default;

  void OnRead(int result) { results_.push_back(result); }
  void OnGetLength(int64_t result) { results_.push_back(result); }

  base::WeakPtr<EventLogger> GetWeakPtr() {
    return weak_ptr_factory_.GetWeakPtr();
  }

  const std::vector<int64_t>& results() const { return results_; }

 private:
  std::vector<int64_t> results_;
  base::WeakPtrFactory<EventLogger> weak_ptr_factory_{this};
};

// Creates a cracked FileSystemURL for tests.
storage::FileSystemURL CreateFileSystemURL(const std::string& mount_point_name,
                                           const base::FilePath& file_path) {
  const std::string origin = std::string("chrome-extension://") + kExtensionId;
  const storage::ExternalMountPoints* const mount_points =
      storage::ExternalMountPoints::GetSystemInstance();
  return mount_points->CreateCrackedFileSystemURL(
      blink::StorageKey::CreateFromStringForTesting(origin),
      storage::kFileSystemTypeExternal,
      base::FilePath::FromUTF8Unsafe(mount_point_name).Append(file_path));
}

}  // namespace

class FileSystemProviderFileStreamReader : public testing::Test {
 protected:
  FileSystemProviderFileStreamReader()
      : profile_(nullptr), fake_file_(nullptr) {}
  ~FileSystemProviderFileStreamReader() override = default;

  void SetUp() override {
    ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
    profile_manager_ = std::make_unique<TestingProfileManager>(
        TestingBrowserProcess::GetGlobal());
    ASSERT_TRUE(profile_manager_->SetUp());
    profile_ = profile_manager_->CreateTestingProfile("testing-profile");

    Service* service = Service::Get(profile_);  // Owned by its factory.
    service->RegisterProvider(FakeExtensionProvider::Create(kExtensionId));

    const base::File::Error result = service->MountFileSystem(
        kProviderId, MountOptions(kFileSystemId, "Testing File System"));
    ASSERT_EQ(base::File::FILE_OK, result);
    FakeProvidedFileSystem* provided_file_system =
        static_cast<FakeProvidedFileSystem*>(
            service->GetProvidedFileSystem(kProviderId, kFileSystemId));
    ASSERT_TRUE(provided_file_system);
    fake_file_ = provided_file_system->GetEntry(base::FilePath(kFakeFilePath));
    ASSERT_TRUE(fake_file_);
    const ProvidedFileSystemInfo& file_system_info =
        service->GetProvidedFileSystem(kProviderId, kFileSystemId)
            ->GetFileSystemInfo();
    const std::string mount_point_name =
        file_system_info.mount_path().BaseName().AsUTF8Unsafe();

    file_url_ = CreateFileSystemURL(mount_point_name,
                                    base::FilePath(kFakeFilePath + 1));
    ASSERT_TRUE(file_url_.is_valid());
    wrong_file_url_ = CreateFileSystemURL(
        mount_point_name, base::FilePath(FILE_PATH_LITERAL("im-not-here.txt")));
    ASSERT_TRUE(wrong_file_url_.is_valid());
  }

  content::BrowserTaskEnvironment task_environment_;
  base::ScopedTempDir data_dir_;
  std::unique_ptr<TestingProfileManager> profile_manager_;
  raw_ptr<TestingProfile> profile_;  // Owned by TestingProfileManager.
  raw_ptr<const FakeEntry> fake_file_;  // Owned by FakePRovidedFileSystem.
  storage::FileSystemURL file_url_;
  storage::FileSystemURL wrong_file_url_;
};

TEST_F(FileSystemProviderFileStreamReader, Read_AllAtOnce) {
  EventLogger logger;

  const int64_t initial_offset = 0;
  FileStreamReader reader(nullptr, file_url_, initial_offset,
                          *fake_file_->metadata->modification_time);
  auto io_buffer = base::MakeRefCounted<net::IOBufferWithSize>(
      base::checked_cast<size_t>(*fake_file_->metadata->size));

  const int result =
      reader.Read(io_buffer.get(), *fake_file_->metadata->size,
                  base::BindOnce(&EventLogger::OnRead, logger.GetWeakPtr()));
  EXPECT_EQ(net::ERR_IO_PENDING, result);
  base::RunLoop().RunUntilIdle();

  ASSERT_EQ(1u, logger.results().size());
  EXPECT_LT(0, logger.results()[0]);
  EXPECT_EQ(*fake_file_->metadata->size, logger.results()[0]);

  std::string buffer_as_string(io_buffer->data(), *fake_file_->metadata->size);
  EXPECT_EQ(fake_file_->contents, buffer_as_string);
}

TEST_F(FileSystemProviderFileStreamReader, Read_WrongFile) {
  EventLogger logger;

  const int64_t initial_offset = 0;
  FileStreamReader reader(nullptr, wrong_file_url_, initial_offset,
                          *fake_file_->metadata->modification_time);
  auto io_buffer = base::MakeRefCounted<net::IOBufferWithSize>(
      base::checked_cast<size_t>(*fake_file_->metadata->size));

  const int result =
      reader.Read(io_buffer.get(), *fake_file_->metadata->size,
                  base::BindOnce(&EventLogger::OnRead, logger.GetWeakPtr()));
  EXPECT_EQ(net::ERR_IO_PENDING, result);
  base::RunLoop().RunUntilIdle();

  ASSERT_EQ(1u, logger.results().size());
  EXPECT_EQ(net::ERR_FILE_NOT_FOUND, logger.results()[0]);
}

TEST_F(FileSystemProviderFileStreamReader, Read_InChunks) {
  EventLogger logger;

  const int64_t initial_offset = 0;
  FileStreamReader reader(nullptr, file_url_, initial_offset,
                          *fake_file_->metadata->modification_time);

  for (int64_t offset = 0; offset < *fake_file_->metadata->size; ++offset) {
    auto io_buffer = base::MakeRefCounted<net::IOBufferWithSize>(1);
    const int result =
        reader.Read(io_buffer.get(), 1,
                    base::BindOnce(&EventLogger::OnRead, logger.GetWeakPtr()));
    EXPECT_EQ(net::ERR_IO_PENDING, result);
    base::RunLoop().RunUntilIdle();
    ASSERT_EQ(offset + 1, static_cast<int64_t>(logger.results().size()));
    EXPECT_EQ(1, logger.results()[offset]);
    EXPECT_EQ(fake_file_->contents[offset], io_buffer->data()[0]);
  }
}

TEST_F(FileSystemProviderFileStreamReader, Read_Slice) {
  EventLogger logger;

  // Trim first 3 and last 3 characters.
  const int64_t initial_offset = 3;
  const int length = *fake_file_->metadata->size - initial_offset - 3;
  ASSERT_GT(*fake_file_->metadata->size, initial_offset);
  ASSERT_LT(0, length);

  FileStreamReader reader(nullptr, file_url_, initial_offset,
                          *fake_file_->metadata->modification_time);
  auto io_buffer = base::MakeRefCounted<net::IOBufferWithSize>(length);

  const int result =
      reader.Read(io_buffer.get(), length,
                  base::BindOnce(&EventLogger::OnRead, logger.GetWeakPtr()));
  EXPECT_EQ(net::ERR_IO_PENDING, result);
  base::RunLoop().RunUntilIdle();

  ASSERT_EQ(1u, logger.results().size());
  EXPECT_EQ(length, logger.results()[0]);

  std::string buffer_as_string(io_buffer->data(), length);
  std::string expected_buffer(fake_file_->contents.data() + initial_offset,
                              length);
  EXPECT_EQ(expected_buffer, buffer_as_string);
}

TEST_F(FileSystemProviderFileStreamReader, Read_Beyond) {
  EventLogger logger;

  // Request reading 1KB more than available.
  const int64_t initial_offset = 0;
  const int length = *fake_file_->metadata->size + 1024;

  FileStreamReader reader(nullptr, file_url_, initial_offset,
                          *fake_file_->metadata->modification_time);
  auto io_buffer = base::MakeRefCounted<net::IOBufferWithSize>(length);

  const int result =
      reader.Read(io_buffer.get(), length,
                  base::BindOnce(&EventLogger::OnRead, logger.GetWeakPtr()));
  EXPECT_EQ(net::ERR_IO_PENDING, result);
  base::RunLoop().RunUntilIdle();

  ASSERT_EQ(1u, logger.results().size());
  EXPECT_LT(0, logger.results()[0]);
  EXPECT_EQ(*fake_file_->metadata->size, logger.results()[0]);

  std::string buffer_as_string(io_buffer->data(), *fake_file_->metadata->size);
  EXPECT_EQ(fake_file_->contents, buffer_as_string);
}

TEST_F(FileSystemProviderFileStreamReader, Read_ModifiedFile) {
  EventLogger logger;

  const int64_t initial_offset = 0;
  FileStreamReader reader(nullptr, file_url_, initial_offset,
                          base::Time::Max());

  auto io_buffer = base::MakeRefCounted<net::IOBufferWithSize>(
      base::checked_cast<size_t>(*fake_file_->metadata->size));
  const int result =
      reader.Read(io_buffer.get(), *fake_file_->metadata->size,
                  base::BindOnce(&EventLogger::OnRead, logger.GetWeakPtr()));

  EXPECT_EQ(net::ERR_IO_PENDING, result);
  base::RunLoop().RunUntilIdle();

  ASSERT_EQ(1u, logger.results().size());
  EXPECT_EQ(net::ERR_UPLOAD_FILE_CHANGED, logger.results()[0]);
}

TEST_F(FileSystemProviderFileStreamReader, Read_ExpectedModificationTimeNull) {
  EventLogger logger;

  const int64_t initial_offset = 0;
  FileStreamReader reader(nullptr, file_url_, initial_offset, base::Time());

  auto io_buffer = base::MakeRefCounted<net::IOBufferWithSize>(
      base::checked_cast<size_t>(*fake_file_->metadata->size));
  const int result =
      reader.Read(io_buffer.get(), *fake_file_->metadata->size,
                  base::BindOnce(&EventLogger::OnRead, logger.GetWeakPtr()));

  EXPECT_EQ(net::ERR_IO_PENDING, result);
  base::RunLoop().RunUntilIdle();

  ASSERT_EQ(1u, logger.results().size());
  EXPECT_EQ(*fake_file_->metadata->size, logger.results()[0]);

  std::string buffer_as_string(io_buffer->data(), *fake_file_->metadata->size);
  EXPECT_EQ(fake_file_->contents, buffer_as_string);
}

TEST_F(FileSystemProviderFileStreamReader, GetLength) {
  EventLogger logger;

  const int64_t initial_offset = 0;
  FileStreamReader reader(nullptr, file_url_, initial_offset,
                          *fake_file_->metadata->modification_time);

  const int result = reader.GetLength(
      base::BindOnce(&EventLogger::OnGetLength, logger.GetWeakPtr()));
  EXPECT_EQ(net::ERR_IO_PENDING, result);
  base::RunLoop().RunUntilIdle();

  ASSERT_EQ(1u, logger.results().size());
  EXPECT_LT(0, logger.results()[0]);
  EXPECT_EQ(*fake_file_->metadata->size, logger.results()[0]);
}

TEST_F(FileSystemProviderFileStreamReader, GetLength_WrongFile) {
  EventLogger logger;

  const int64_t initial_offset = 0;
  FileStreamReader reader(nullptr, wrong_file_url_, initial_offset,
                          *fake_file_->metadata->modification_time);

  const int result = reader.GetLength(
      base::BindOnce(&EventLogger::OnGetLength, logger.GetWeakPtr()));
  EXPECT_EQ(net::ERR_IO_PENDING, result);
  base::RunLoop().RunUntilIdle();

  ASSERT_EQ(1u, logger.results().size());
  EXPECT_EQ(net::ERR_FILE_NOT_FOUND, logger.results()[0]);
}

TEST_F(FileSystemProviderFileStreamReader, GetLength_ModifiedFile) {
  EventLogger logger;

  const int64_t initial_offset = 0;
  FileStreamReader reader(nullptr, file_url_, initial_offset,
                          base::Time::Max());

  const int result = reader.GetLength(
      base::BindOnce(&EventLogger::OnGetLength, logger.GetWeakPtr()));
  EXPECT_EQ(net::ERR_IO_PENDING, result);
  base::RunLoop().RunUntilIdle();

  ASSERT_EQ(1u, logger.results().size());
  EXPECT_EQ(net::ERR_UPLOAD_FILE_CHANGED, logger.results()[0]);
}

TEST_F(FileSystemProviderFileStreamReader,
       GetLength_ExpectedModificationTimeNull) {
  EventLogger logger;

  const int64_t initial_offset = 0;
  FileStreamReader reader(nullptr, file_url_, initial_offset, base::Time());

  const int result = reader.GetLength(
      base::BindOnce(&EventLogger::OnGetLength, logger.GetWeakPtr()));
  EXPECT_EQ(net::ERR_IO_PENDING, result);
  base::RunLoop().RunUntilIdle();

  ASSERT_EQ(1u, logger.results().size());
  EXPECT_LT(0, logger.results()[0]);
  EXPECT_EQ(*fake_file_->metadata->size, logger.results()[0]);
}

}  // namespace ash::file_system_provider