1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "base/message_loop/message_loop.h"
6#include "base/process/process.h"
7#include "base/stl_util.h"
8#include "base/strings/string_util.h"
9#include "content/public/test/test_browser_thread.h"
10#include "extensions/browser/extension_function.h"
11#include "extensions/browser/quota_service.h"
12#include "testing/gtest/include/gtest/gtest.h"
13
14using base::TimeDelta;
15using base::TimeTicks;
16using content::BrowserThread;
17
18namespace extensions {
19
20typedef QuotaLimitHeuristic::Bucket Bucket;
21typedef QuotaLimitHeuristic::Config Config;
22typedef QuotaLimitHeuristic::BucketList BucketList;
23typedef QuotaService::TimedLimit TimedLimit;
24typedef QuotaService::SustainedLimit SustainedLimit;
25
26namespace {
27
28const char kGenericName[] = "name";
29const Config kFrozenConfig = {0, TimeDelta::FromDays(0)};
30const Config k2PerMinute = {2, TimeDelta::FromMinutes(1)};
31const Config k20PerHour = {20, TimeDelta::FromHours(1)};
32const TimeTicks kStartTime = TimeTicks();
33const TimeTicks k1MinuteAfterStart = kStartTime + TimeDelta::FromMinutes(1);
34
35class Mapper : public QuotaLimitHeuristic::BucketMapper {
36 public:
37  Mapper() {}
38  virtual ~Mapper() { STLDeleteValues(&buckets_); }
39  virtual void GetBucketsForArgs(const base::ListValue* args,
40                                 BucketList* buckets) OVERRIDE {
41    for (size_t i = 0; i < args->GetSize(); i++) {
42      int id;
43      ASSERT_TRUE(args->GetInteger(i, &id));
44      if (buckets_.find(id) == buckets_.end())
45        buckets_[id] = new Bucket();
46      buckets->push_back(buckets_[id]);
47    }
48  }
49
50 private:
51  typedef std::map<int, Bucket*> BucketMap;
52  BucketMap buckets_;
53  DISALLOW_COPY_AND_ASSIGN(Mapper);
54};
55
56class MockMapper : public QuotaLimitHeuristic::BucketMapper {
57 public:
58  virtual void GetBucketsForArgs(const base::ListValue* args,
59                                 BucketList* buckets) OVERRIDE {}
60};
61
62class MockFunction : public ExtensionFunction {
63 public:
64  explicit MockFunction(const std::string& name) { set_name(name); }
65
66  virtual void SetArgs(const base::ListValue* args) OVERRIDE {}
67  virtual std::string GetError() const OVERRIDE { return std::string(); }
68  virtual void SetError(const std::string& error) OVERRIDE {}
69  virtual void Destruct() const OVERRIDE { delete this; }
70  virtual ResponseAction Run() OVERRIDE { return RespondLater(); }
71  virtual void SendResponse(bool) OVERRIDE {}
72
73 protected:
74  virtual ~MockFunction() {}
75};
76
77class TimedLimitMockFunction : public MockFunction {
78 public:
79  explicit TimedLimitMockFunction(const std::string& name)
80      : MockFunction(name) {}
81  virtual void GetQuotaLimitHeuristics(QuotaLimitHeuristics* heuristics) const
82      OVERRIDE {
83    heuristics->push_back(
84        new TimedLimit(k2PerMinute, new Mapper(), kGenericName));
85  }
86
87 private:
88  virtual ~TimedLimitMockFunction() {}
89};
90
91class ChainedLimitsMockFunction : public MockFunction {
92 public:
93  explicit ChainedLimitsMockFunction(const std::string& name)
94      : MockFunction(name) {}
95  virtual void GetQuotaLimitHeuristics(QuotaLimitHeuristics* heuristics) const
96      OVERRIDE {
97    // No more than 2 per minute sustained over 5 minutes.
98    heuristics->push_back(new SustainedLimit(
99        TimeDelta::FromMinutes(5), k2PerMinute, new Mapper(), kGenericName));
100    // No more than 20 per hour.
101    heuristics->push_back(
102        new TimedLimit(k20PerHour, new Mapper(), kGenericName));
103  }
104
105 private:
106  virtual ~ChainedLimitsMockFunction() {}
107};
108
109class FrozenMockFunction : public MockFunction {
110 public:
111  explicit FrozenMockFunction(const std::string& name) : MockFunction(name) {}
112  virtual void GetQuotaLimitHeuristics(QuotaLimitHeuristics* heuristics) const
113      OVERRIDE {
114    heuristics->push_back(
115        new TimedLimit(kFrozenConfig, new Mapper(), kGenericName));
116  }
117
118 private:
119  virtual ~FrozenMockFunction() {}
120};
121}  // namespace
122
123class QuotaServiceTest : public testing::Test {
124 public:
125  QuotaServiceTest()
126      : extension_a_("a"),
127        extension_b_("b"),
128        extension_c_("c"),
129        loop_(),
130        ui_thread_(BrowserThread::UI, &loop_) {}
131  virtual void SetUp() { service_.reset(new QuotaService()); }
132  virtual void TearDown() {
133    loop_.RunUntilIdle();
134    service_.reset();
135  }
136
137 protected:
138  std::string extension_a_;
139  std::string extension_b_;
140  std::string extension_c_;
141  scoped_ptr<QuotaService> service_;
142  base::MessageLoop loop_;
143  content::TestBrowserThread ui_thread_;
144};
145
146class QuotaLimitHeuristicTest : public testing::Test {
147 public:
148  static void DoMoreThan2PerMinuteFor5Minutes(const TimeTicks& start_time,
149                                              QuotaLimitHeuristic* lim,
150                                              Bucket* b,
151                                              int an_unexhausted_minute) {
152    for (int i = 0; i < 5; i++) {
153      // Perform one operation in each minute.
154      int m = i * 60;
155      EXPECT_TRUE(lim->Apply(b, start_time + TimeDelta::FromSeconds(10 + m)));
156      EXPECT_TRUE(b->has_tokens());
157
158      if (i == an_unexhausted_minute)
159        continue;  // Don't exhaust all tokens this minute.
160
161      EXPECT_TRUE(lim->Apply(b, start_time + TimeDelta::FromSeconds(15 + m)));
162      EXPECT_FALSE(b->has_tokens());
163
164      // These are OK because we haven't exhausted all buckets.
165      EXPECT_TRUE(lim->Apply(b, start_time + TimeDelta::FromSeconds(20 + m)));
166      EXPECT_FALSE(b->has_tokens());
167      EXPECT_TRUE(lim->Apply(b, start_time + TimeDelta::FromSeconds(50 + m)));
168      EXPECT_FALSE(b->has_tokens());
169    }
170  }
171};
172
173TEST_F(QuotaLimitHeuristicTest, Timed) {
174  TimedLimit lim(k2PerMinute, new MockMapper(), kGenericName);
175  Bucket b;
176
177  b.Reset(k2PerMinute, kStartTime);
178  EXPECT_TRUE(lim.Apply(&b, kStartTime));
179  EXPECT_TRUE(b.has_tokens());
180  EXPECT_TRUE(lim.Apply(&b, kStartTime + TimeDelta::FromSeconds(30)));
181  EXPECT_FALSE(b.has_tokens());
182  EXPECT_FALSE(lim.Apply(&b, k1MinuteAfterStart));
183
184  b.Reset(k2PerMinute, kStartTime);
185  EXPECT_TRUE(lim.Apply(&b, k1MinuteAfterStart - TimeDelta::FromSeconds(1)));
186  EXPECT_TRUE(lim.Apply(&b, k1MinuteAfterStart));
187  EXPECT_TRUE(lim.Apply(&b, k1MinuteAfterStart + TimeDelta::FromSeconds(1)));
188  EXPECT_TRUE(lim.Apply(&b, k1MinuteAfterStart + TimeDelta::FromSeconds(2)));
189  EXPECT_FALSE(lim.Apply(&b, k1MinuteAfterStart + TimeDelta::FromSeconds(3)));
190}
191
192TEST_F(QuotaLimitHeuristicTest, Sustained) {
193  SustainedLimit lim(
194      TimeDelta::FromMinutes(5), k2PerMinute, new MockMapper(), kGenericName);
195  Bucket bucket;
196
197  bucket.Reset(k2PerMinute, kStartTime);
198  DoMoreThan2PerMinuteFor5Minutes(kStartTime, &lim, &bucket, -1);
199  // This straw breaks the camel's back.
200  EXPECT_FALSE(lim.Apply(&bucket, kStartTime + TimeDelta::FromMinutes(6)));
201
202  // The heuristic resets itself on a safe request.
203  EXPECT_TRUE(lim.Apply(&bucket, kStartTime + TimeDelta::FromDays(1)));
204
205  // Do the same as above except don't exhaust final bucket.
206  bucket.Reset(k2PerMinute, kStartTime);
207  DoMoreThan2PerMinuteFor5Minutes(kStartTime, &lim, &bucket, -1);
208  EXPECT_TRUE(lim.Apply(&bucket, kStartTime + TimeDelta::FromMinutes(7)));
209
210  // Do the same as above except don't exhaust the 3rd (w.l.o.g) bucket.
211  bucket.Reset(k2PerMinute, kStartTime);
212  DoMoreThan2PerMinuteFor5Minutes(kStartTime, &lim, &bucket, 3);
213  // If the 3rd bucket were exhausted, this would fail (see first test).
214  EXPECT_TRUE(lim.Apply(&bucket, kStartTime + TimeDelta::FromMinutes(6)));
215}
216
217TEST_F(QuotaServiceTest, NoHeuristic) {
218  scoped_refptr<MockFunction> f(new MockFunction("foo"));
219  base::ListValue args;
220  EXPECT_EQ("", service_->Assess(extension_a_, f.get(), &args, kStartTime));
221}
222
223TEST_F(QuotaServiceTest, FrozenHeuristic) {
224  scoped_refptr<MockFunction> f(new FrozenMockFunction("foo"));
225  base::ListValue args;
226  args.Append(new base::FundamentalValue(1));
227  EXPECT_NE("", service_->Assess(extension_a_, f.get(), &args, kStartTime));
228}
229
230TEST_F(QuotaServiceTest, SingleHeuristic) {
231  scoped_refptr<MockFunction> f(new TimedLimitMockFunction("foo"));
232  base::ListValue args;
233  args.Append(new base::FundamentalValue(1));
234  EXPECT_EQ("", service_->Assess(extension_a_, f.get(), &args, kStartTime));
235  EXPECT_EQ("",
236            service_->Assess(extension_a_,
237                             f.get(),
238                             &args,
239                             kStartTime + TimeDelta::FromSeconds(10)));
240  EXPECT_NE("",
241            service_->Assess(extension_a_,
242                             f.get(),
243                             &args,
244                             kStartTime + TimeDelta::FromSeconds(15)));
245
246  base::ListValue args2;
247  args2.Append(new base::FundamentalValue(1));
248  args2.Append(new base::FundamentalValue(2));
249  EXPECT_EQ("", service_->Assess(extension_b_, f.get(), &args2, kStartTime));
250  EXPECT_EQ("",
251            service_->Assess(extension_b_,
252                             f.get(),
253                             &args2,
254                             kStartTime + TimeDelta::FromSeconds(10)));
255
256  TimeDelta peace = TimeDelta::FromMinutes(30);
257  EXPECT_EQ("",
258            service_->Assess(extension_b_, f.get(), &args, kStartTime + peace));
259  EXPECT_EQ("",
260            service_->Assess(extension_b_,
261                             f.get(),
262                             &args,
263                             kStartTime + peace + TimeDelta::FromSeconds(10)));
264  EXPECT_NE("",
265            service_->Assess(extension_b_,
266                             f.get(),
267                             &args2,
268                             kStartTime + peace + TimeDelta::FromSeconds(15)));
269
270  // Test that items are independent.
271  base::ListValue args3;
272  args3.Append(new base::FundamentalValue(3));
273  EXPECT_EQ("", service_->Assess(extension_c_, f.get(), &args, kStartTime));
274  EXPECT_EQ("",
275            service_->Assess(extension_c_,
276                             f.get(),
277                             &args3,
278                             kStartTime + TimeDelta::FromSeconds(10)));
279  EXPECT_EQ("",
280            service_->Assess(extension_c_,
281                             f.get(),
282                             &args,
283                             kStartTime + TimeDelta::FromSeconds(15)));
284  EXPECT_EQ("",
285            service_->Assess(extension_c_,
286                             f.get(),
287                             &args3,
288                             kStartTime + TimeDelta::FromSeconds(20)));
289  EXPECT_NE("",
290            service_->Assess(extension_c_,
291                             f.get(),
292                             &args,
293                             kStartTime + TimeDelta::FromSeconds(25)));
294  EXPECT_NE("",
295            service_->Assess(extension_c_,
296                             f.get(),
297                             &args3,
298                             kStartTime + TimeDelta::FromSeconds(30)));
299}
300
301TEST_F(QuotaServiceTest, ChainedHeuristics) {
302  scoped_refptr<MockFunction> f(new ChainedLimitsMockFunction("foo"));
303  base::ListValue args;
304  args.Append(new base::FundamentalValue(1));
305
306  // First, test that the low limit can be avoided but the higher one is hit.
307  // One event per minute for 20 minutes comes in under the sustained limit,
308  // but is equal to the timed limit.
309  for (int i = 0; i < 20; i++) {
310    EXPECT_EQ(
311        "",
312        service_->Assess(extension_a_,
313                         f.get(),
314                         &args,
315                         kStartTime + TimeDelta::FromSeconds(10 + i * 60)));
316  }
317
318  // This will bring us to 21 events in an hour, which is a violation.
319  EXPECT_NE("",
320            service_->Assess(extension_a_,
321                             f.get(),
322                             &args,
323                             kStartTime + TimeDelta::FromMinutes(30)));
324
325  // Now, check that we can still hit the lower limit.
326  for (int i = 0; i < 5; i++) {
327    EXPECT_EQ(
328        "",
329        service_->Assess(extension_b_,
330                         f.get(),
331                         &args,
332                         kStartTime + TimeDelta::FromSeconds(10 + i * 60)));
333    EXPECT_EQ(
334        "",
335        service_->Assess(extension_b_,
336                         f.get(),
337                         &args,
338                         kStartTime + TimeDelta::FromSeconds(15 + i * 60)));
339    EXPECT_EQ(
340        "",
341        service_->Assess(extension_b_,
342                         f.get(),
343                         &args,
344                         kStartTime + TimeDelta::FromSeconds(20 + i * 60)));
345  }
346
347  EXPECT_NE("",
348            service_->Assess(extension_b_,
349                             f.get(),
350                             &args,
351                             kStartTime + TimeDelta::FromMinutes(6)));
352}
353
354TEST_F(QuotaServiceTest, MultipleFunctionsDontInterfere) {
355  scoped_refptr<MockFunction> f(new TimedLimitMockFunction("foo"));
356  scoped_refptr<MockFunction> g(new TimedLimitMockFunction("bar"));
357
358  base::ListValue args_f;
359  base::ListValue args_g;
360  args_f.Append(new base::FundamentalValue(1));
361  args_g.Append(new base::FundamentalValue(2));
362
363  EXPECT_EQ("", service_->Assess(extension_a_, f.get(), &args_f, kStartTime));
364  EXPECT_EQ("", service_->Assess(extension_a_, g.get(), &args_g, kStartTime));
365  EXPECT_EQ("",
366            service_->Assess(extension_a_,
367                             f.get(),
368                             &args_f,
369                             kStartTime + TimeDelta::FromSeconds(10)));
370  EXPECT_EQ("",
371            service_->Assess(extension_a_,
372                             g.get(),
373                             &args_g,
374                             kStartTime + TimeDelta::FromSeconds(10)));
375  EXPECT_NE("",
376            service_->Assess(extension_a_,
377                             f.get(),
378                             &args_f,
379                             kStartTime + TimeDelta::FromSeconds(15)));
380  EXPECT_NE("",
381            service_->Assess(extension_a_,
382                             g.get(),
383                             &args_g,
384                             kStartTime + TimeDelta::FromSeconds(15)));
385}
386
387TEST_F(QuotaServiceTest, ViolatorsWillBeViolators) {
388  scoped_refptr<MockFunction> f(new TimedLimitMockFunction("foo"));
389  scoped_refptr<MockFunction> g(new TimedLimitMockFunction("bar"));
390  base::ListValue arg;
391  arg.Append(new base::FundamentalValue(1));
392  EXPECT_EQ("", service_->Assess(extension_a_, f.get(), &arg, kStartTime));
393  EXPECT_EQ("",
394            service_->Assess(extension_a_,
395                             f.get(),
396                             &arg,
397                             kStartTime + TimeDelta::FromSeconds(10)));
398  EXPECT_NE("",
399            service_->Assess(extension_a_,
400                             f.get(),
401                             &arg,
402                             kStartTime + TimeDelta::FromSeconds(15)));
403
404  // We don't allow this extension to use quota limited functions even if they
405  // wait a while.
406  EXPECT_NE(
407      "",
408      service_->Assess(
409          extension_a_, f.get(), &arg, kStartTime + TimeDelta::FromDays(1)));
410  EXPECT_NE(
411      "",
412      service_->Assess(
413          extension_a_, g.get(), &arg, kStartTime + TimeDelta::FromDays(1)));
414}
415
416}  // namespace extensions
417