910e62b5创建于 1月15日历史提交
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// This file tests the chrome.alarms extension API.

#include "extensions/browser/api/alarms/alarms_api.h"

#include <stddef.h>

#include <array>
#include <memory>
#include <optional>
#include <string>
#include <utility>

#include "base/functional/bind.h"
#include "base/json/json_reader.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/simple_test_clock.h"
#include "base/test/values_test_util.h"
#include "base/values.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/fake_local_frame.h"
#include "extensions/browser/api/alarms/alarm_manager.h"
#include "extensions/browser/api/alarms/alarms_api_constants.h"
#include "extensions/browser/api_unittest.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_id.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

typedef extensions::api::alarms::Alarm JsAlarm;

namespace extensions {

namespace {

// Test delegate which quits the message loop when an alarm fires.
class AlarmDelegate : public AlarmManager::Delegate {
 public:
  ~AlarmDelegate() override {}
  void OnAlarm(const ExtensionId& extension_id, const Alarm& alarm) override {
    alarms_seen.push_back(alarm.js_alarm->name);
    if (!quit_closure_.is_null()) {
      std::move(quit_closure_).Run();
    }
  }
  void WaitForAlarm() {
    base::RunLoop loop;
    quit_closure_ = loop.QuitClosure();
    loop.Run();
  }
  std::vector<std::string> alarms_seen;
  base::OnceClosure quit_closure_;
};

}  // namespace

void RunScheduleNextPoll(AlarmManager* alarm_manager) {
  alarm_manager->ScheduleNextPoll();
}

class ExtensionAlarmsTest : public ApiUnitTest {
 public:
  using ApiUnitTest::RunFunction;

  void SetUp() override {
    ApiUnitTest::SetUp();

    alarm_manager_ = AlarmManager::Get(browser_context());
    alarm_manager_->SetClockForTesting(&test_clock_);

    auto delegate = std::make_unique<AlarmDelegate>();
    alarm_delegate_ = delegate.get();
    alarm_manager_->set_delegate(std::move(delegate));

    test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(10));
  }

  void TearDown() override {
    // Drop unowned references before superclass destroys them.
    alarm_delegate_ = nullptr;
    alarm_manager_ = nullptr;
    ApiUnitTest::TearDown();
  }

  void CreateAlarm(const std::string& args) {
    RunFunction(new AlarmsCreateFunction(&test_clock_), args);
  }

  // Takes a JSON result from a function and converts it to a vector of
  // JsAlarms.
  std::vector<JsAlarm> ToAlarmList(const std::optional<base::Value>& value) {
    std::vector<JsAlarm> list;
    if (!value) {
      return list;
    }
    for (const auto& item : value->GetList()) {
      auto alarm = JsAlarm::FromValue(item);
      if (!alarm) {
        ADD_FAILURE() << "Failed to parse JsAlarm." << item;
        return list;
      }
      list.push_back(std::move(alarm).value());
    }
    return list;
  }

  // Creates up to 3 alarms using the extension API.
  void CreateAlarms(size_t num_alarms) {
    CHECK_LE(num_alarms, 3U);

    static constexpr std::array kCreateArgs = {
        "[null, {\"periodInMinutes\": 0.001}]",
        "[\"7\", {\"periodInMinutes\": 7}]",
        "[\"0\", {\"delayInMinutes\": 0}]",
    };
    for (size_t i = 0; i < num_alarms; ++i) {
      std::optional<base::Value> result = RunFunctionAndReturnValue(
          new AlarmsCreateFunction(&test_clock_), kCreateArgs[i]);
      EXPECT_FALSE(result);
    }
  }

  base::SimpleTestClock test_clock_;
  raw_ptr<AlarmManager> alarm_manager_;
  raw_ptr<AlarmDelegate> alarm_delegate_;
};

void ExtensionAlarmsTestGetAllAlarmsCallback(
    const AlarmManager::AlarmList* alarms) {
  // Ensure the alarm is gone.
  ASSERT_FALSE(alarms);
}

void ExtensionAlarmsTestGetAlarmCallback(ExtensionAlarmsTest* test,
                                         Alarm* alarm) {
  ASSERT_TRUE(alarm);
  EXPECT_EQ("", alarm->js_alarm->name);
  EXPECT_DOUBLE_EQ(10000, alarm->js_alarm->scheduled_time);
  EXPECT_FALSE(alarm->js_alarm->period_in_minutes);

  // Now wait for the alarm to fire. Our test delegate will quit the
  // MessageLoop when that happens.
  test->alarm_delegate_->WaitForAlarm();

  ASSERT_EQ(1u, test->alarm_delegate_->alarms_seen.size());
  EXPECT_EQ("", test->alarm_delegate_->alarms_seen[0]);

  // Ensure the alarm is gone.
  test->alarm_manager_->GetAllAlarms(
      test->extension()->id(),
      base::BindOnce(ExtensionAlarmsTestGetAllAlarmsCallback));
}

TEST_F(ExtensionAlarmsTest, Create) {
  test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(10));
  // Create 1 non-repeating alarm.
  CreateAlarm("[null, {\"delayInMinutes\": 0}]");

  alarm_manager_->GetAlarm(
      extension()->id(), std::string(),
      base::BindOnce(ExtensionAlarmsTestGetAlarmCallback, this));
}

void ExtensionAlarmsTestCreateRepeatingGetAlarmCallback(
    ExtensionAlarmsTest* test,
    Alarm* alarm) {
  ASSERT_TRUE(alarm);
  EXPECT_EQ("", alarm->js_alarm->name);
  EXPECT_DOUBLE_EQ(10060, alarm->js_alarm->scheduled_time);
  EXPECT_THAT(alarm->js_alarm->period_in_minutes, testing::Eq(0.001));

  test->test_clock_.Advance(base::Seconds(1));
  // Now wait for the alarm to fire. Our test delegate will quit the
  // MessageLoop when that happens.
  test->alarm_delegate_->WaitForAlarm();

  test->test_clock_.Advance(base::Seconds(1));
  // Wait again, and ensure the alarm fires again.
  RunScheduleNextPoll(test->alarm_manager_);
  test->alarm_delegate_->WaitForAlarm();

  ASSERT_EQ(2u, test->alarm_delegate_->alarms_seen.size());
  EXPECT_EQ("", test->alarm_delegate_->alarms_seen[0]);
}

TEST_F(ExtensionAlarmsTest, CreateRepeating) {
  test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(10));

  // Create 1 repeating alarm.
  CreateAlarm("[null, {\"periodInMinutes\": 0.001}]");

  alarm_manager_->GetAlarm(
      extension()->id(), std::string(),
      base::BindOnce(ExtensionAlarmsTestCreateRepeatingGetAlarmCallback, this));
}

void ExtensionAlarmsTestCreateAbsoluteGetAlarm2Callback(
    ExtensionAlarmsTest* test,
    Alarm* alarm) {
  ASSERT_FALSE(alarm);

  ASSERT_EQ(1u, test->alarm_delegate_->alarms_seen.size());
  EXPECT_EQ("", test->alarm_delegate_->alarms_seen[0]);
}

void ExtensionAlarmsTestCreateAbsoluteGetAlarm1Callback(
    ExtensionAlarmsTest* test,
    Alarm* alarm) {
  ASSERT_TRUE(alarm);
  EXPECT_EQ("", alarm->js_alarm->name);
  EXPECT_DOUBLE_EQ(10001, alarm->js_alarm->scheduled_time);
  EXPECT_FALSE(alarm->js_alarm->period_in_minutes.has_value());

  test->test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(10.1));
  // Now wait for the alarm to fire. Our test delegate will quit the
  // MessageLoop when that happens.
  test->alarm_delegate_->WaitForAlarm();

  test->alarm_manager_->GetAlarm(
      test->extension()->id(), std::string(),
      base::BindOnce(ExtensionAlarmsTestCreateAbsoluteGetAlarm2Callback, test));
}

TEST_F(ExtensionAlarmsTest, CreateAbsolute) {
  test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(9.99));
  CreateAlarm("[null, {\"when\": 10001}]");

  alarm_manager_->GetAlarm(
      extension()->id(), std::string(),
      base::BindOnce(ExtensionAlarmsTestCreateAbsoluteGetAlarm1Callback, this));
}

void ExtensionAlarmsTestCreateRepeatingWithQuickFirstCallGetAlarm3Callback(
    ExtensionAlarmsTest* test,
    Alarm* alarm) {
  ASSERT_TRUE(alarm);
  EXPECT_THAT(test->alarm_delegate_->alarms_seen, testing::ElementsAre("", ""));
}

void ExtensionAlarmsTestCreateRepeatingWithQuickFirstCallGetAlarm2Callback(
    ExtensionAlarmsTest* test,
    Alarm* alarm) {
  ASSERT_TRUE(alarm);
  EXPECT_THAT(test->alarm_delegate_->alarms_seen, testing::ElementsAre(""));

  test->test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(10.7));

  test->alarm_delegate_->WaitForAlarm();

  test->alarm_manager_->GetAlarm(
      test->extension()->id(), std::string(),
      base::BindOnce(
          ExtensionAlarmsTestCreateRepeatingWithQuickFirstCallGetAlarm3Callback,
          test));
}

void ExtensionAlarmsTestCreateRepeatingWithQuickFirstCallGetAlarm1Callback(
    ExtensionAlarmsTest* test,
    Alarm* alarm) {
  ASSERT_TRUE(alarm);
  EXPECT_EQ("", alarm->js_alarm->name);
  EXPECT_DOUBLE_EQ(10001, alarm->js_alarm->scheduled_time);
  EXPECT_THAT(alarm->js_alarm->period_in_minutes, testing::Eq(0.001));

  test->test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(10.1));
  // Now wait for the alarm to fire. Our test delegate will quit the
  // MessageLoop when that happens.
  test->alarm_delegate_->WaitForAlarm();

  test->alarm_manager_->GetAlarm(
      test->extension()->id(), std::string(),
      base::BindOnce(
          ExtensionAlarmsTestCreateRepeatingWithQuickFirstCallGetAlarm2Callback,
          test));
}

TEST_F(ExtensionAlarmsTest, CreateRepeatingWithQuickFirstCall) {
  test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(9.99));
  CreateAlarm("[null, {\"when\": 10001, \"periodInMinutes\": 0.001}]");

  alarm_manager_->GetAlarm(
      extension()->id(), std::string(),
      base::BindOnce(
          ExtensionAlarmsTestCreateRepeatingWithQuickFirstCallGetAlarm1Callback,
          this));
}

void ExtensionAlarmsTestCreateDupeGetAllAlarmsCallback(
    const AlarmManager::AlarmList* alarms) {
  ASSERT_TRUE(alarms);
  EXPECT_EQ(1u, alarms->size());
  EXPECT_DOUBLE_EQ(430000, (*alarms)[0].js_alarm->scheduled_time);
}

TEST_F(ExtensionAlarmsTest, CreateDupe) {
  test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(10));

  // Create 2 duplicate alarms. The first should be overridden.
  CreateAlarm("[\"dup\", {\"delayInMinutes\": 1}]");
  CreateAlarm("[\"dup\", {\"delayInMinutes\": 7}]");

  alarm_manager_->GetAllAlarms(
      extension()->id(),
      base::BindOnce(ExtensionAlarmsTestCreateDupeGetAllAlarmsCallback));
}

class ConsoleLogMessageLocalFrame : public content::FakeLocalFrame {
 public:
  void AddMessageToConsole(blink::mojom::ConsoleMessageLevel level,
                           const std::string& message,
                           bool discard_duplicates) override {
    message_count_++;
    last_level_ = level;
    last_message_ = message;
  }
  unsigned message_count() const { return message_count_; }
  const std::string& last_message() const { return last_message_; }
  blink::mojom::ConsoleMessageLevel last_level() const {
    return last_level_.value();
  }

 private:
  unsigned message_count_ = 0;
  std::optional<blink::mojom::ConsoleMessageLevel> last_level_;
  std::string last_message_;
};

class ExtensionAlarmsLogTest : public ExtensionAlarmsTest {
  void SetUp() override {
    ExtensionAlarmsTest::SetUp();

    // Make sure there's a RenderViewHost for alarms to warn into.
    CreateExtensionPage();
  }
};

TEST_F(ExtensionAlarmsLogTest, CreateDelayBelowMinimum) {
  // Create an alarm with delay below the minimum accepted value.
  ConsoleLogMessageLocalFrame local_frame;
  local_frame.Init(
      contents()->GetPrimaryMainFrame()->GetRemoteAssociatedInterfaces());
  base::RunLoop().RunUntilIdle();
  ASSERT_EQ(local_frame.message_count(), 0u);
  CreateAlarm("[\"negative\", {\"delayInMinutes\": -0.2}]");
  base::RunLoop().RunUntilIdle();
  ASSERT_EQ(local_frame.message_count(), 1u);

  EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kWarning,
            local_frame.last_level());
  EXPECT_THAT(local_frame.last_message(),
              testing::HasSubstr(
                  "delay is less than the minimum duration of 30 seconds"));
}

TEST_F(ExtensionAlarmsTest, Get) {
  test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(4));

  // Create 2 alarms, and make sure we can query them.
  CreateAlarms(2);

  // Get the default one.
  {
    std::optional<base::Value> result =
        RunFunctionAndReturnValue(new AlarmsGetFunction(), "[null]");
    ASSERT_TRUE(result);
    ASSERT_TRUE(result->is_dict());
    auto alarm = JsAlarm::FromValue(result->GetDict());
    EXPECT_TRUE(alarm);
    EXPECT_EQ("", alarm->name);
    EXPECT_DOUBLE_EQ(4060, alarm->scheduled_time);
    EXPECT_THAT(alarm->period_in_minutes, testing::Eq(0.001));
  }

  // Get "7".
  {
    std::optional<base::Value> result =
        RunFunctionAndReturnValue(new AlarmsGetFunction(), "[\"7\"]");
    ASSERT_TRUE(result);
    ASSERT_TRUE(result->is_dict());
    auto alarm = JsAlarm::FromValue(result->GetDict());
    EXPECT_TRUE(alarm);
    EXPECT_EQ("7", alarm->name);
    EXPECT_EQ(424000, alarm->scheduled_time);
    EXPECT_THAT(alarm->period_in_minutes, testing::Eq(7));
  }

  // Get a non-existent one.
  {
    std::optional<base::Value> result =
        RunFunctionAndReturnValue(new AlarmsGetFunction(), "[\"nobody\"]");
    ASSERT_FALSE(result);
  }
}

TEST_F(ExtensionAlarmsTest, GetAll) {
  // Test getAll with 0 alarms.
  {
    std::optional<base::Value> result =
        RunFunctionAndReturnValue(new AlarmsGetAllFunction(), "[]");
    std::vector<JsAlarm> alarms = ToAlarmList(result);
    EXPECT_EQ(0u, alarms.size());
  }

  // Create 2 alarms, and make sure we can query them.
  CreateAlarms(2);

  {
    std::optional<base::Value> result =
        RunFunctionAndReturnValue(new AlarmsGetAllFunction(), "[null]");
    std::vector<JsAlarm> alarms = ToAlarmList(result);
    EXPECT_EQ(2u, alarms.size());

    // Test the "7" alarm.
    JsAlarm* alarm = &alarms[0];
    if (alarm->name != "7") {
      alarm = &alarms[1];
    }
    EXPECT_EQ("7", alarm->name);
    EXPECT_THAT(alarm->period_in_minutes, testing::Eq(7));
  }
}

void ExtensionAlarmsTestClearGetAllAlarms2Callback(
    const AlarmManager::AlarmList* alarms) {
  // Ensure the 0.001-minute alarm is still there, since it's repeating.
  ASSERT_TRUE(alarms);
  EXPECT_EQ(1u, alarms->size());
  EXPECT_THAT((*alarms)[0].js_alarm->period_in_minutes, testing::Eq(0.001));
}

void ExtensionAlarmsTestClearGetAllAlarms1Callback(
    ExtensionAlarmsTest* test,
    const AlarmManager::AlarmList* alarms) {
  ASSERT_TRUE(alarms);
  EXPECT_EQ(1u, alarms->size());
  EXPECT_THAT((*alarms)[0].js_alarm->period_in_minutes, testing::Eq(0.001));

  // Now wait for the alarms to fire, and ensure the cancelled alarms don't
  // fire.
  test->test_clock_.Advance(base::Milliseconds(60));
  RunScheduleNextPoll(test->alarm_manager_);

  test->alarm_delegate_->WaitForAlarm();

  ASSERT_EQ(1u, test->alarm_delegate_->alarms_seen.size());
  EXPECT_EQ("", test->alarm_delegate_->alarms_seen[0]);

  // Ensure the 0.001-minute alarm is still there, since it's repeating.
  test->alarm_manager_->GetAllAlarms(
      test->extension()->id(),
      base::BindOnce(ExtensionAlarmsTestClearGetAllAlarms2Callback));
}

TEST_F(ExtensionAlarmsTest, Clear) {
  // Clear a non-existent one.
  {
    std::optional<base::Value> result =
        RunFunctionAndReturnValue(new AlarmsClearFunction(), "[\"nobody\"]");
    ASSERT_TRUE(result->is_bool());
    EXPECT_FALSE(result->GetBool());
  }

  // Create 3 alarms.
  CreateAlarms(3);

  // Clear all but the 0.001-minute alarm.
  {
    std::optional<base::Value> result =
        RunFunctionAndReturnValue(new AlarmsClearFunction(), "[\"7\"]");
    ASSERT_TRUE(result->is_bool());
    EXPECT_TRUE(result->GetBool());
  }
  {
    std::optional<base::Value> result =
        RunFunctionAndReturnValue(new AlarmsClearFunction(), "[\"0\"]");
    ASSERT_TRUE(result->is_bool());
    EXPECT_TRUE(result->GetBool());
  }

  alarm_manager_->GetAllAlarms(
      extension()->id(),
      base::BindOnce(ExtensionAlarmsTestClearGetAllAlarms1Callback, this));
}

void ExtensionAlarmsTestClearAllGetAllAlarms2Callback(
    const AlarmManager::AlarmList* alarms) {
  ASSERT_FALSE(alarms);
}

void ExtensionAlarmsTestClearAllGetAllAlarms1Callback(
    ExtensionAlarmsTest* test,
    const AlarmManager::AlarmList* alarms) {
  ASSERT_TRUE(alarms);
  EXPECT_EQ(3u, alarms->size());

  // Clear them.
  test->RunFunction(new AlarmsClearAllFunction(), "[]");
  test->alarm_manager_->GetAllAlarms(
      test->extension()->id(),
      base::BindOnce(ExtensionAlarmsTestClearAllGetAllAlarms2Callback));
}

TEST_F(ExtensionAlarmsTest, ClearAll) {
  // ClearAll with no alarms set.
  {
    std::optional<base::Value> result =
        RunFunctionAndReturnValue(new AlarmsClearAllFunction(), "[]");
    ASSERT_TRUE(result->is_bool());
    EXPECT_TRUE(result->GetBool());
  }

  // Create 3 alarms.
  CreateAlarms(3);
  alarm_manager_->GetAllAlarms(
      extension()->id(),
      base::BindOnce(ExtensionAlarmsTestClearAllGetAllAlarms1Callback, this));
}

class ExtensionAlarmsSchedulingTest : public ExtensionAlarmsTest {
  void GetAlarmCallback(Alarm* alarm) {
    CHECK(alarm);
    const base::Time scheduled_time =
        base::Time::FromMillisecondsSinceUnixEpoch(
            alarm->js_alarm->scheduled_time);
    EXPECT_EQ(scheduled_time, alarm_manager_->next_poll_time_);
  }

  static void RemoveAlarmCallback(bool found) { EXPECT_TRUE(found); }
  static void RemoveAllAlarmsCallback() {}

 public:
  // Get the time that the alarm named is scheduled to run.
  void VerifyScheduledTime(const std::string& alarm_name) {
    alarm_manager_->GetAlarm(
        extension()->id(), alarm_name,
        base::BindOnce(&ExtensionAlarmsSchedulingTest::GetAlarmCallback,
                       base::Unretained(this)));
  }

  void RemoveAlarm(const std::string& name) {
    alarm_manager_->RemoveAlarm(
        extension()->id(), name,
        base::BindOnce(&ExtensionAlarmsSchedulingTest::RemoveAlarmCallback));
  }

  void RemoveAllAlarms() {
    alarm_manager_->RemoveAllAlarms(
        extension()->id(),
        base::BindOnce(
            &ExtensionAlarmsSchedulingTest::RemoveAllAlarmsCallback));
  }
};

TEST_F(ExtensionAlarmsSchedulingTest, PollScheduling) {
  {
    CreateAlarm("[\"a\", {\"periodInMinutes\": 6}]");
    CreateAlarm("[\"bb\", {\"periodInMinutes\": 8}]");
    VerifyScheduledTime("a");
    RemoveAllAlarms();
  }
  {
    CreateAlarm("[\"a\", {\"delayInMinutes\": 10}]");
    CreateAlarm("[\"bb\", {\"delayInMinutes\": 21}]");
    VerifyScheduledTime("a");
    RemoveAllAlarms();
  }
  {
    test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(10));
    CreateAlarm("[\"a\", {\"periodInMinutes\": 10}]");
    Alarm alarm;
    alarm.js_alarm->name = "bb";
    alarm.js_alarm->scheduled_time = 30 * 60000;
    alarm.js_alarm->period_in_minutes = 30;
    alarm_manager_->AddAlarmImpl(extension()->id(), std::move(alarm));
    VerifyScheduledTime("a");
    RemoveAllAlarms();
  }
  {
    test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(3 * 60 + 1));
    Alarm alarm;
    alarm.js_alarm->name = "bb";
    alarm.js_alarm->scheduled_time = 3 * 60000;
    alarm.js_alarm->period_in_minutes = 3;
    alarm_manager_->AddAlarmImpl(extension()->id(), std::move(alarm));

    alarm_delegate_->WaitForAlarm();

    EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(3 * 60) + base::Minutes(3),
              alarm_manager_->next_poll_time_);
    RemoveAllAlarms();
  }
  {
    test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(4 * 60 + 1));
    CreateAlarm("[\"a\", {\"periodInMinutes\": 2}]");
    RemoveAlarm("a");
    Alarm alarm2;
    alarm2.js_alarm->name = "bb";
    alarm2.js_alarm->scheduled_time = 4 * 60000;
    alarm2.js_alarm->period_in_minutes = 4;
    alarm_manager_->AddAlarmImpl(extension()->id(), std::move(alarm2));
    Alarm alarm3;
    alarm3.js_alarm->name = "ccc";
    alarm3.js_alarm->scheduled_time = 25 * 60000;
    alarm3.js_alarm->period_in_minutes = 25;
    alarm_manager_->AddAlarmImpl(extension()->id(), std::move(alarm3));
    alarm_delegate_->WaitForAlarm();
    EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(4 * 60) + base::Minutes(4),
              alarm_manager_->next_poll_time_);
    RemoveAllAlarms();
  }
}

TEST_F(ExtensionAlarmsSchedulingTest, ReleasedExtensionPollsInfrequently) {
  set_extension(ExtensionBuilder("Test")
                    .SetLocation(mojom::ManifestLocation::kInternal)
                    .Build());
  test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(300));
  CreateAlarm("[\"a\", {\"when\": 300010}]");
  CreateAlarm("[\"b\", {\"when\": 340000}]");

  // On startup (when there's no "last poll"), we let alarms fire as
  // soon as they're scheduled.
  EXPECT_DOUBLE_EQ(
      300010, alarm_manager_->next_poll_time_.InMillisecondsFSinceUnixEpoch());

  alarm_manager_->last_poll_time_ = base::Time::FromSecondsSinceUnixEpoch(290);
  // In released extensions, we set the granularity to at least 30
  // seconds, which makes AddAlarm schedule the next poll after the
  // extension requested.
  alarm_manager_->ScheduleNextPoll();
  EXPECT_DOUBLE_EQ(
      (alarm_manager_->last_poll_time_ + base::Seconds(30))
          .InMillisecondsFSinceUnixEpoch(),
      alarm_manager_->next_poll_time_.InMillisecondsFSinceUnixEpoch());
}

TEST_F(ExtensionAlarmsSchedulingTest, TimerRunning) {
  EXPECT_FALSE(alarm_manager_->timer_.IsRunning());
  CreateAlarm("[\"a\", {\"delayInMinutes\": 0.001}]");
  EXPECT_TRUE(alarm_manager_->timer_.IsRunning());
  test_clock_.Advance(base::Milliseconds(60));
  alarm_delegate_->WaitForAlarm();
  EXPECT_FALSE(alarm_manager_->timer_.IsRunning());
  CreateAlarm("[\"bb\", {\"delayInMinutes\": 10}]");
  EXPECT_TRUE(alarm_manager_->timer_.IsRunning());
  RemoveAllAlarms();
  EXPECT_FALSE(alarm_manager_->timer_.IsRunning());
}

TEST_F(ExtensionAlarmsSchedulingTest, MinimumGranularity) {
  set_extension(ExtensionBuilder("Test")
                    .SetLocation(mojom::ManifestLocation::kInternal)
                    .Build());
  test_clock_.SetNow(base::Time::UnixEpoch());
  CreateAlarm("[\"a\", {\"periodInMinutes\": 2}]");
  test_clock_.Advance(base::Seconds(1));
  CreateAlarm("[\"b\", {\"periodInMinutes\": 2}]");
  test_clock_.Advance(base::Minutes(2));

  alarm_manager_->last_poll_time_ =
      base::Time::FromSecondsSinceUnixEpoch(2 * 60);
  // In released extensions, we set the granularity to at least 30
  // seconds, which makes scheduler set it to 30 seconds, rather than
  // 1 second later (when b is supposed to go off).
  alarm_manager_->ScheduleNextPoll();
  EXPECT_DOUBLE_EQ(
      (alarm_manager_->last_poll_time_ + base::Seconds(30))
          .InMillisecondsFSinceUnixEpoch(),
      alarm_manager_->next_poll_time_.InMillisecondsFSinceUnixEpoch());
}

TEST_F(ExtensionAlarmsSchedulingTest, DifferentMinimumGranularities) {
  test_clock_.SetNow(base::Time::UnixEpoch());
  // Create an alarm to go off in 12 seconds. This uses the default, unpacked
  // extension - so there is no minimum granularity.
  CreateAlarm("[\"a\", {\"periodInMinutes\": 0.2}]");  // 12 seconds.

  // Create a new extension, which is packed, and has a granularity of 30
  // seconds. CreateAlarm() uses extension_, so keep a ref of the old one
  // around, and repopulate extension_.
  scoped_refptr<const Extension> extension2(extension_ref());
  set_extension(ExtensionBuilder("Test")
                    .SetLocation(mojom::ManifestLocation::kInternal)
                    .Build());

  CreateAlarm("[\"b\", {\"periodInMinutes\": 2}]");

  alarm_manager_->last_poll_time_ = base::Time::UnixEpoch();
  alarm_manager_->ScheduleNextPoll();

  // The next poll time should be 12 seconds from now - the time at which the
  // first alarm should go off.
  EXPECT_DOUBLE_EQ(
      (alarm_manager_->last_poll_time_ + base::Seconds(12))
          .InMillisecondsFSinceUnixEpoch(),
      alarm_manager_->next_poll_time_.InMillisecondsFSinceUnixEpoch());
}

void FrequencyTestGetAlarmsCallback(ExtensionAlarmsTest* test, Alarm* alarm) {
  ASSERT_TRUE(alarm);
  EXPECT_EQ("hello", alarm->js_alarm->name);
  EXPECT_DOUBLE_EQ(10000, alarm->js_alarm->scheduled_time);
  EXPECT_THAT(alarm->js_alarm->period_in_minutes, testing::Eq(0.0001));

  test->test_clock_.Advance(base::Milliseconds(10));
  // Now wait for the alarm to fire. Our test delegate will quit the
  // MessageLoop when that happens.
  test->alarm_delegate_->WaitForAlarm();
}

// Tests that alarms with very small period written to storage are also
// subjected to minimum polling interval.
// Regression test for https://crbug.com/618540.
TEST_F(ExtensionAlarmsSchedulingTest, PollFrequencyFromStoredAlarm) {
  static constexpr struct {
    bool is_unpacked;
    int manifest_version;
    base::TimeDelta delay_minimum;
  } test_data[] = {
      {true, 2, alarms_api_constants::kDevDelayMinimum},
      {true, 3, alarms_api_constants::kDevDelayMinimum},
      {false, 2, alarms_api_constants::kMV2ReleaseDelayMinimum},
      {false, 3, alarms_api_constants::kMV3ReleaseDelayMinimum},
      {false, 4, alarms_api_constants::kMV3ReleaseDelayMinimum},
  };

  // Test once for unpacked and once for crx extension.
  for (const auto& entry : test_data) {
    test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(10));

    // Mimic retrieving an alarm from StateStore.
    std::string alarm_args =
        "[{\"name\": \"hello\", \"scheduledTime\": 10000, "
        "\"periodInMinutes\": 0.0001}]";
    base::TimeDelta min_delay = alarms_api_constants::GetMinimumDelay(
        entry.is_unpacked, entry.manifest_version);

    alarm_manager_->ReadFromStorage(extension()->id(), min_delay,
                                    base::test::ParseJson(alarm_args));

    // Let the alarm fire once, we will verify the next polling time afterwards.
    alarm_manager_->GetAlarm(
        extension()->id(), "hello",
        base::BindOnce(FrequencyTestGetAlarmsCallback, this));

    // The stored alarm's "periodInMinutes" is much smaller than allowed minimum
    // in this test (alarms_api_constants::kDevDelayMinimum or
    // alarms_api_constants::kReleaseDelayMinimum). Make sure
    // our next poll time corresponds to our allowed minimum and not to the
    // StateStore specified "periodInMinutes".
    base::Time expected_poll_time =
        // 10s initial clock.
        base::Time::FromSecondsSinceUnixEpoch(10) +
        // 10ms in FrequencyTestGetAlarmsCallback.
        base::Milliseconds(10) + entry.delay_minimum;
    // The alarm should not trigger before our expected poll time...
    EXPECT_GE(alarm_manager_->next_poll_time_, expected_poll_time);
    // And should trigger within a few seconds of it (to account for test
    // differences).
    EXPECT_LT(alarm_manager_->next_poll_time_,
              expected_poll_time + base::Seconds(10));
    RemoveAlarm("hello");
  }
}

// Test that scheduled alarms go off at set intervals, even if their actual
// trigger is off.
TEST_F(ExtensionAlarmsSchedulingTest, RepeatingAlarmsScheduledPredictably) {
  test_clock_.SetNow(base::Time::UnixEpoch());
  CreateAlarm("[\"a\", {\"periodInMinutes\": 2}]");

  alarm_manager_->last_poll_time_ = base::Time::UnixEpoch();
  alarm_manager_->ScheduleNextPoll();

  // We expect the first poll to happen two minutes from the start.
  EXPECT_DOUBLE_EQ(
      (alarm_manager_->last_poll_time_ + base::Seconds(120))
          .InMillisecondsFSinceUnixEpoch(),
      alarm_manager_->next_poll_time_.InMillisecondsFSinceUnixEpoch());

  // Poll more than two minutes later.
  test_clock_.Advance(base::Seconds(125));
  alarm_manager_->PollAlarms();

  // The alarm should have triggered once.
  EXPECT_EQ(1u, alarm_delegate_->alarms_seen.size());

  // The next poll should still be scheduled for four minutes from the start,
  // even though this is less than two minutes since the last alarm.
  // Last poll was at 125 seconds; next poll should be at 240 seconds.
  EXPECT_DOUBLE_EQ(
      (alarm_manager_->last_poll_time_ + base::Seconds(115))
          .InMillisecondsFSinceUnixEpoch(),
      alarm_manager_->next_poll_time_.InMillisecondsFSinceUnixEpoch());

  // Completely miss a scheduled trigger.
  test_clock_.Advance(base::Seconds(255));  // Total Time: 380s
  alarm_manager_->PollAlarms();

  // The alarm should have triggered again at this last poll.
  EXPECT_EQ(2u, alarm_delegate_->alarms_seen.size());

  // The next poll should be the first poll that hasn't happened and is in-line
  // with the original scheduling.
  // Last poll was at 380 seconds; next poll should be at 480 seconds.
  EXPECT_DOUBLE_EQ(
      (alarm_manager_->last_poll_time_ + base::Seconds(100))
          .InMillisecondsFSinceUnixEpoch(),
      alarm_manager_->next_poll_time_.InMillisecondsFSinceUnixEpoch());
}

}  // namespace extensions