chromium/chrome/browser/ash/fileapi/diversion_file_manager_unittest.cc

// Copyright 2024 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/fileapi/diversion_file_manager.h"

#include "base/files/file_util.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/browser_task_environment.h"
#include "net/base/io_buffer.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {

using FinishDivertingResult = DiversionFileManager::FinishDivertingResult;
using StartDivertingResult = DiversionFileManager::StartDivertingResult;
using StoppedReason = DiversionFileManager::StoppedReason;

namespace {

DiversionFileManager::Callback IncrementCounterCallback(
    StoppedReason expected_stopped_reason,
    const storage::FileSystemURL& expected_url,
    int* counter,
    int delta) {
  return base::BindOnce(
      [](StoppedReason expected_stopped_reason,
         const storage::FileSystemURL& expected_url, int* counter, int delta,
         StoppedReason stopped_reason, const storage::FileSystemURL& url,
         base::ScopedFD scoped_fd, int64_t file_size, base::File::Error error) {
        EXPECT_EQ(expected_stopped_reason, stopped_reason);
        EXPECT_EQ(expected_url, url);
        *counter += delta;
      },
      expected_stopped_reason, expected_url, counter, delta);
}

}  // namespace

class DiversionFileManagerTest : public testing::Test {
 public:
  DiversionFileManagerTest()
      : task_environment_(base::test::TaskEnvironment::MainThreadType::IO,
                          base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}

 protected:
  void SynchronousWrite(storage::FileStreamWriter& writer, std::string s) {
    scoped_refptr<net::StringIOBuffer> buffer =
        base::MakeRefCounted<net::StringIOBuffer>(s);
    writer.Write(
        buffer.get(), buffer->size(),
        base::BindOnce([](base::RepeatingClosure quit_closure,
                          int byte_count_or_error_code) { quit_closure.Run(); },
                       task_environment_.QuitClosure()));
    task_environment_.RunUntilQuit();
  }

  content::BrowserTaskEnvironment task_environment_;
};

TEST_F(DiversionFileManagerTest, ImplicitExplicitFinish) {
  ASSERT_TRUE(
      ::content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));

  scoped_refptr<DiversionFileManager> dfm =
      base::MakeRefCounted<DiversionFileManager>();
  storage::FileSystemURL bar_url = storage::FileSystemURL::CreateForTest(
      GURL("filesystem:chrome-extension://abc/external/p/q/bar"));
  storage::FileSystemURL foo_url = storage::FileSystemURL::CreateForTest(
      GURL("filesystem:chrome-extension://abc/external/p/q/foo"));
  EXPECT_FALSE(dfm->IsDiverting(bar_url));
  EXPECT_FALSE(dfm->IsDiverting(foo_url));

  int bar_counter = 0;
  int foo_counter = 0;

  ASSERT_EQ(
      StartDivertingResult::kOK,
      dfm->StartDiverting(bar_url, base::Seconds(28),
                          IncrementCounterCallback(StoppedReason::kImplicitIdle,
                                                   bar_url, &bar_counter, 1)));
  ASSERT_EQ(
      StartDivertingResult::kOK,
      dfm->StartDiverting(foo_url, base::Seconds(32),
                          IncrementCounterCallback(StoppedReason::kImplicitIdle,
                                                   foo_url, &foo_counter, 10)));
  EXPECT_TRUE(dfm->IsDiverting(bar_url));
  EXPECT_TRUE(dfm->IsDiverting(foo_url));

  task_environment_.FastForwardBy(base::Seconds(30));
  EXPECT_FALSE(dfm->IsDiverting(bar_url));
  EXPECT_TRUE(dfm->IsDiverting(foo_url));

  ASSERT_EQ(FinishDivertingResult::kWasNotDiverting,
            dfm->FinishDiverting(bar_url, IncrementCounterCallback(
                                              StoppedReason::kExplicitFinish,
                                              bar_url, &bar_counter, 100)));
  ASSERT_EQ(FinishDivertingResult::kOK,
            dfm->FinishDiverting(foo_url, IncrementCounterCallback(
                                              StoppedReason::kExplicitFinish,
                                              foo_url, &foo_counter, 1000)));
  EXPECT_FALSE(dfm->IsDiverting(bar_url));
  EXPECT_FALSE(dfm->IsDiverting(foo_url));

  EXPECT_EQ(bar_counter, 1);
  EXPECT_EQ(foo_counter, 1000);
}

TEST_F(DiversionFileManagerTest, ReaderKeepsDiversionAlive) {
  ASSERT_TRUE(
      ::content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));

  scoped_refptr<DiversionFileManager> dfm =
      base::MakeRefCounted<DiversionFileManager>();
  storage::FileSystemURL foo_url = storage::FileSystemURL::CreateForTest(
      GURL("filesystem:chrome-extension://abc/external/p/q/foo"));

  int foo_counter = 0;

  ASSERT_EQ(
      StartDivertingResult::kOK,
      dfm->StartDiverting(foo_url, base::Seconds(15),
                          IncrementCounterCallback(StoppedReason::kImplicitIdle,
                                                   foo_url, &foo_counter, 1)));

  task_environment_.FastForwardBy(base::Seconds(10));
  EXPECT_TRUE(dfm->IsDiverting(foo_url));

  std::unique_ptr<storage::FileStreamReader> reader =
      dfm->CreateDivertedFileStreamReader(foo_url, 0);

  task_environment_.FastForwardBy(base::Seconds(10));
  EXPECT_TRUE(dfm->IsDiverting(foo_url));
  EXPECT_EQ(foo_counter, 0);

  task_environment_.FastForwardBy(base::Seconds(10));
  EXPECT_TRUE(dfm->IsDiverting(foo_url));
  EXPECT_EQ(foo_counter, 0);

  reader.reset();

  task_environment_.FastForwardBy(base::Seconds(10));
  EXPECT_TRUE(dfm->IsDiverting(foo_url));
  EXPECT_EQ(foo_counter, 0);

  task_environment_.FastForwardBy(base::Seconds(10));
  EXPECT_FALSE(dfm->IsDiverting(foo_url));
  EXPECT_EQ(foo_counter, 1);
}

TEST_F(DiversionFileManagerTest, Writes) {
  ASSERT_TRUE(
      ::content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));

  scoped_refptr<DiversionFileManager> dfm =
      base::MakeRefCounted<DiversionFileManager>();
  storage::FileSystemURL foo_url = storage::FileSystemURL::CreateForTest(
      GURL("filesystem:chrome-extension://abc/external/p/q/foo"));

  base::FilePath temp_dir;
  ASSERT_TRUE(base::GetTempDir(&temp_dir));
  dfm->OverrideTmpfileDirForTesting(temp_dir);

  static constexpr auto on_implicit_idle =
      [](StoppedReason stopped_reason, const storage::FileSystemURL& url,
         base::ScopedFD scoped_fd, int64_t file_size, base::File::Error error) {
        // We shouldn't get here. We should get to on_explicit_finish instead.
        NOTREACHED();
      };
  ASSERT_EQ(StartDivertingResult::kOK,
            dfm->StartDiverting(foo_url, base::Seconds(15),
                                base::BindOnce(on_implicit_idle)));

  dfm->TruncateDivertedFile(foo_url, 0,
                            base::BindOnce([](base::File::Error result) {
                              EXPECT_EQ(base::File::FILE_OK, result);
                            }));

  std::unique_ptr<storage::FileStreamWriter> writer =
      dfm->CreateDivertedFileStreamWriter(foo_url, 0);

  SynchronousWrite(*writer, "hi ");
  dfm->GetDivertedFileInfo(
      foo_url, {storage::FileSystemOperation::GetMetadataField::kSize},
      base::BindOnce(
          [](base::File::Error result, const base::File::Info& file_info) {
            EXPECT_EQ(base::File::FILE_OK, result);
            EXPECT_EQ(3, file_info.size);
          }));

  task_environment_.FastForwardBy(base::Seconds(20));
  EXPECT_TRUE(dfm->IsDiverting(foo_url));

  SynchronousWrite(*writer, "there.");
  dfm->GetDivertedFileInfo(
      foo_url, {storage::FileSystemOperation::GetMetadataField::kSize},
      base::BindOnce(
          [](base::File::Error result, const base::File::Info& file_info) {
            EXPECT_EQ(base::File::FILE_OK, result);
            EXPECT_EQ(9, file_info.size);
          }));

  task_environment_.FastForwardBy(base::Seconds(20));
  EXPECT_TRUE(dfm->IsDiverting(foo_url));

  bool on_explicit_finish_called = false;
  static constexpr auto on_explicit_finish =
      [](bool* called, StoppedReason stopped_reason,
         const storage::FileSystemURL& url, base::ScopedFD scoped_fd,
         int64_t file_size, base::File::Error error) {
        ASSERT_TRUE(scoped_fd.is_valid());
        EXPECT_EQ(file_size, 9u);
        EXPECT_EQ(base::File::FILE_OK, error);
        char buf[9] = {0};
        EXPECT_TRUE(base::ReadFromFD(scoped_fd.get(), buf));
        EXPECT_EQ(buf, base::span_from_cstring("hi there."));
        *called = true;
      };
  ASSERT_EQ(FinishDivertingResult::kOK,
            dfm->FinishDiverting(foo_url,
                                 base::BindOnce(on_explicit_finish,
                                                &on_explicit_finish_called)));

  task_environment_.FastForwardBy(base::Seconds(20));
  EXPECT_FALSE(dfm->IsDiverting(foo_url));
  EXPECT_FALSE(on_explicit_finish_called);

  writer.reset();
  EXPECT_FALSE(dfm->IsDiverting(foo_url));
  EXPECT_TRUE(on_explicit_finish_called);
}

}  // namespace ash