Home | History | Annotate | Download | only in download
      1 // Copyright (c) 2012 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 "chrome/browser/download/download_item_model.h"
      6 
      7 #include <vector>
      8 
      9 #include "base/i18n/rtl.h"
     10 #include "base/logging.h"
     11 #include "base/message_loop/message_loop.h"
     12 #include "base/strings/string16.h"
     13 #include "base/strings/string_util.h"
     14 #include "base/strings/utf_string_conversions.h"
     15 #include "content/public/test/mock_download_item.h"
     16 #include "extensions/common/extension.h"
     17 #include "testing/gmock/include/gmock/gmock.h"
     18 #include "testing/gtest/include/gtest/gtest.h"
     19 #include "ui/base/resource/resource_bundle.h"
     20 #include "ui/base/text/bytes_formatting.h"
     21 #include "ui/gfx/font_list.h"
     22 #include "ui/gfx/text_utils.h"
     23 
     24 using content::DownloadItem;
     25 using ::testing::Mock;
     26 using ::testing::NiceMock;
     27 using ::testing::Return;
     28 using ::testing::ReturnRefOfCopy;
     29 using ::testing::SetArgPointee;
     30 using ::testing::_;
     31 
     32 namespace {
     33 
     34 // Create a char array that has as many elements as there are download
     35 // interrupt reasons. We can then use that in a COMPILE_ASSERT to make sure
     36 // that all the interrupt reason codes are accounted for. The reason codes are
     37 // unfortunately sparse, making this necessary.
     38 char kInterruptReasonCounter[] = {
     39   0,                                // content::DOWNLOAD_INTERRUPT_REASON_NONE
     40 #define INTERRUPT_REASON(name,value) 0,
     41 #include "content/public/browser/download_interrupt_reason_values.h"
     42 #undef INTERRUPT_REASON
     43 };
     44 const size_t kInterruptReasonCount = ARRAYSIZE_UNSAFE(kInterruptReasonCounter);
     45 
     46 // Default target path for a mock download item in DownloadItemModelTest.
     47 const base::FilePath::CharType kDefaultTargetFilePath[] =
     48     FILE_PATH_LITERAL("/foo/bar/foo.bar");
     49 
     50 const base::FilePath::CharType kDefaultDisplayFileName[] =
     51     FILE_PATH_LITERAL("foo.bar");
     52 
     53 // Default URL for a mock download item in DownloadItemModelTest.
     54 const char kDefaultURL[] = "http://example.com/foo.bar";
     55 
     56 class DownloadItemModelTest : public testing::Test {
     57  public:
     58   DownloadItemModelTest()
     59       : model_(&item_) {}
     60 
     61   virtual ~DownloadItemModelTest() {
     62   }
     63 
     64  protected:
     65   // Sets up defaults for the download item and sets |model_| to a new
     66   // DownloadItemModel that uses the mock download item.
     67   void SetupDownloadItemDefaults() {
     68     ON_CALL(item_, GetReceivedBytes()).WillByDefault(Return(1));
     69     ON_CALL(item_, GetTotalBytes()).WillByDefault(Return(2));
     70     ON_CALL(item_, TimeRemaining(_)).WillByDefault(Return(false));
     71     ON_CALL(item_, GetMimeType()).WillByDefault(Return("text/html"));
     72     ON_CALL(item_, AllDataSaved()).WillByDefault(Return(false));
     73     ON_CALL(item_, GetOpenWhenComplete()).WillByDefault(Return(false));
     74     ON_CALL(item_, GetFileExternallyRemoved()).WillByDefault(Return(false));
     75     ON_CALL(item_, GetState())
     76         .WillByDefault(Return(DownloadItem::IN_PROGRESS));
     77     ON_CALL(item_, GetURL())
     78         .WillByDefault(ReturnRefOfCopy(GURL(kDefaultURL)));
     79     ON_CALL(item_, GetFileNameToReportUser())
     80         .WillByDefault(Return(base::FilePath(kDefaultDisplayFileName)));
     81     ON_CALL(item_, GetTargetFilePath())
     82         .WillByDefault(ReturnRefOfCopy(base::FilePath(kDefaultTargetFilePath)));
     83     ON_CALL(item_, GetTargetDisposition())
     84         .WillByDefault(
     85             Return(DownloadItem::TARGET_DISPOSITION_OVERWRITE));
     86     ON_CALL(item_, IsPaused()).WillByDefault(Return(false));
     87   }
     88 
     89   void SetupInterruptedDownloadItem(content::DownloadInterruptReason reason) {
     90     EXPECT_CALL(item_, GetLastReason()).WillRepeatedly(Return(reason));
     91     EXPECT_CALL(item_, GetState())
     92         .WillRepeatedly(Return(
     93             (reason == content::DOWNLOAD_INTERRUPT_REASON_NONE) ?
     94                 DownloadItem::IN_PROGRESS :
     95                 DownloadItem::INTERRUPTED));
     96   }
     97 
     98   content::MockDownloadItem& item() {
     99     return item_;
    100   }
    101 
    102   DownloadItemModel& model() {
    103     return model_;
    104   }
    105 
    106  private:
    107   NiceMock<content::MockDownloadItem> item_;
    108   DownloadItemModel model_;
    109 };
    110 
    111 }  // namespace
    112 
    113 TEST_F(DownloadItemModelTest, InterruptedStatus) {
    114   // Test that we have the correct interrupt status message for downloads that
    115   // are in the INTERRUPTED state.
    116   const struct TestCase {
    117     // The reason.
    118     content::DownloadInterruptReason reason;
    119 
    120     // Expected status string. This will include the progress as well.
    121     const char* expected_status;
    122   } kTestCases[] = {
    123     { content::DOWNLOAD_INTERRUPT_REASON_NONE,
    124       "1/2 B" },
    125     { content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED,
    126       "Failed - Download error" },
    127     { content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED,
    128       "Failed - Insufficient permissions" },
    129     { content::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE,
    130       "Failed - Disk full" },
    131     { content::DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG,
    132       "Failed - Path too long" },
    133     { content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE,
    134       "Failed - File too large" },
    135     { content::DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED,
    136       "Failed - Virus detected" },
    137     { content::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED,
    138       "Failed - Blocked" },
    139     { content::DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED,
    140       "Failed - Virus scan failed" },
    141     { content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT,
    142       "Failed - File truncated" },
    143     { content::DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR,
    144       "Failed - System busy" },
    145     { content::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED,
    146       "Failed - Network error" },
    147     { content::DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT,
    148       "Failed - Network timeout" },
    149     { content::DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED,
    150       "Failed - Network disconnected" },
    151     { content::DOWNLOAD_INTERRUPT_REASON_NETWORK_SERVER_DOWN,
    152       "Failed - Server unavailable" },
    153     { content::DOWNLOAD_INTERRUPT_REASON_NETWORK_INVALID_REQUEST,
    154       "Failed - Network error" },
    155     { content::DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED,
    156       "Failed - Server problem" },
    157     { content::DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE,
    158       "Failed - Download error" },
    159     { content::DOWNLOAD_INTERRUPT_REASON_SERVER_PRECONDITION,
    160       "Failed - Download error" },
    161     { content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT,
    162       "Failed - No file" },
    163     { content::DOWNLOAD_INTERRUPT_REASON_SERVER_UNAUTHORIZED,
    164       "Failed - Needs authorization" },
    165     { content::DOWNLOAD_INTERRUPT_REASON_SERVER_CERT_PROBLEM,
    166       "Failed - Bad certificate" },
    167     { content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED,
    168       "Cancelled" },
    169     { content::DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN,
    170       "Failed - Shutdown" },
    171     { content::DOWNLOAD_INTERRUPT_REASON_CRASH,
    172       "Failed - Crash" },
    173   };
    174   COMPILE_ASSERT(kInterruptReasonCount == ARRAYSIZE_UNSAFE(kTestCases),
    175                  interrupt_reason_mismatch);
    176 
    177   SetupDownloadItemDefaults();
    178   for (unsigned i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) {
    179     const TestCase& test_case = kTestCases[i];
    180     SetupInterruptedDownloadItem(test_case.reason);
    181     EXPECT_STREQ(test_case.expected_status,
    182                  base::UTF16ToUTF8(model().GetStatusText()).c_str());
    183   }
    184 }
    185 
    186 // Note: This test is currently skipped on Android. See http://crbug.com/139398
    187 TEST_F(DownloadItemModelTest, InterruptTooltip) {
    188   // Test that we have the correct interrupt tooltip for downloads that are in
    189   // the INTERRUPTED state.
    190   const struct TestCase {
    191     // The reason.
    192     content::DownloadInterruptReason reason;
    193 
    194     // Expected tooltip text. The tooltip text for interrupted downloads
    195     // typically consist of two lines. One for the filename and one for the
    196     // interrupt reason. The returned string contains a newline.
    197     const char* expected_tooltip;
    198   } kTestCases[] = {
    199     { content::DOWNLOAD_INTERRUPT_REASON_NONE,
    200       "foo.bar" },
    201     { content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED,
    202       "foo.bar\nDownload error" },
    203     { content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED,
    204       "foo.bar\nInsufficient permissions" },
    205     { content::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE,
    206       "foo.bar\nDisk full" },
    207     { content::DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG,
    208       "foo.bar\nPath too long" },
    209     { content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE,
    210       "foo.bar\nFile too large" },
    211     { content::DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED,
    212       "foo.bar\nVirus detected" },
    213     { content::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED,
    214       "foo.bar\nBlocked" },
    215     { content::DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED,
    216       "foo.bar\nVirus scan failed" },
    217     { content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT,
    218       "foo.bar\nFile truncated" },
    219     { content::DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR,
    220       "foo.bar\nSystem busy" },
    221     { content::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED,
    222       "foo.bar\nNetwork error" },
    223     { content::DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT,
    224       "foo.bar\nNetwork timeout" },
    225     { content::DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED,
    226       "foo.bar\nNetwork disconnected" },
    227     { content::DOWNLOAD_INTERRUPT_REASON_NETWORK_SERVER_DOWN,
    228       "foo.bar\nServer unavailable" },
    229     { content::DOWNLOAD_INTERRUPT_REASON_NETWORK_INVALID_REQUEST,
    230       "foo.bar\nNetwork error" },
    231     { content::DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED,
    232       "foo.bar\nServer problem" },
    233     { content::DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE,
    234       "foo.bar\nDownload error" },
    235     { content::DOWNLOAD_INTERRUPT_REASON_SERVER_PRECONDITION,
    236       "foo.bar\nDownload error" },
    237     { content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT,
    238       "foo.bar\nNo file" },
    239     { content::DOWNLOAD_INTERRUPT_REASON_SERVER_UNAUTHORIZED,
    240       "foo.bar\nNeeds authorization" },
    241     { content::DOWNLOAD_INTERRUPT_REASON_SERVER_CERT_PROBLEM,
    242       "foo.bar\nBad certificate" },
    243     { content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED,
    244       "foo.bar" },
    245     { content::DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN,
    246       "foo.bar\nShutdown" },
    247     { content::DOWNLOAD_INTERRUPT_REASON_CRASH,
    248       "foo.bar\nCrash" },
    249   };
    250   COMPILE_ASSERT(kInterruptReasonCount == ARRAYSIZE_UNSAFE(kTestCases),
    251                  interrupt_reason_mismatch);
    252 
    253   // Large tooltip width. Should be large enough to accommodate the entire
    254   // tooltip without truncation.
    255   const int kLargeTooltipWidth = 1000;
    256 
    257   // Small tooltip width. Small enough to require truncation of most
    258   // tooltips. Used to test eliding logic.
    259   const int kSmallTooltipWidth = 40;
    260 
    261   const gfx::FontList& font_list =
    262       ui::ResourceBundle::GetSharedInstance().GetFontList(
    263           ui::ResourceBundle::BaseFont);
    264   SetupDownloadItemDefaults();
    265   for (unsigned i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) {
    266     const TestCase& test_case = kTestCases[i];
    267     SetupInterruptedDownloadItem(test_case.reason);
    268 
    269     // GetTooltipText() elides the tooltip so that the text would fit within a
    270     // given width. The following test would fail if kLargeTooltipWidth isn't
    271     // large enough to accomodate all the strings.
    272     EXPECT_STREQ(
    273         test_case.expected_tooltip,
    274         base::UTF16ToUTF8(model().GetTooltipText(font_list,
    275                                                  kLargeTooltipWidth)).c_str());
    276 
    277     // Check that if the width is small, the returned tooltip only contains
    278     // lines of the given width or smaller.
    279     std::vector<base::string16> lines;
    280     base::string16 truncated_tooltip =
    281         model().GetTooltipText(font_list, kSmallTooltipWidth);
    282     Tokenize(truncated_tooltip, base::ASCIIToUTF16("\n"), &lines);
    283     for (unsigned i = 0; i < lines.size(); ++i)
    284       EXPECT_GE(kSmallTooltipWidth, gfx::GetStringWidth(lines[i], font_list));
    285   }
    286 }
    287 
    288 TEST_F(DownloadItemModelTest, InProgressStatus) {
    289   const struct TestCase {
    290     int64 received_bytes;               // Return value of GetReceivedBytes().
    291     int64 total_bytes;                  // Return value of GetTotalBytes().
    292     bool  time_remaining_known;         // If TimeRemaining() is known.
    293     bool  open_when_complete;           // GetOpenWhenComplete().
    294     bool  is_paused;                    // IsPaused().
    295     const char* expected_status;        // Expected status text.
    296   } kTestCases[] = {
    297     // These are all the valid combinations of the above fields for a download
    298     // that is in IN_PROGRESS state. Go through all of them and check the return
    299     // value of DownloadItemModel::GetStatusText(). The point isn't to lock down
    300     // the status strings, but to make sure we end up with something sane for
    301     // all the circumstances we care about.
    302     //
    303     // For GetReceivedBytes()/GetTotalBytes(), we only check whether each is
    304     // non-zero. In addition, if |total_bytes| is zero, then
    305     // |time_remaining_known| is also false.
    306     //
    307     //         .-- .TimeRemaining() is known.
    308     //        |       .-- .GetOpenWhenComplete()
    309     //        |      |      .---- .IsPaused()
    310     { 0, 0, false, false, false, "Starting..." },
    311     { 1, 0, false, false, false, "1 B" },
    312     { 0, 2, false, false, false, "Starting..." },
    313     { 1, 2, false, false, false, "1/2 B" },
    314     { 0, 2, true,  false, false, "0/2 B, 10 secs left" },
    315     { 1, 2, true,  false, false, "1/2 B, 10 secs left" },
    316     { 0, 0, false, true,  false, "Opening when complete" },
    317     { 1, 0, false, true,  false, "Opening when complete" },
    318     { 0, 2, false, true,  false, "Opening when complete" },
    319     { 1, 2, false, true,  false, "Opening when complete" },
    320     { 0, 2, true,  true,  false, "Opening in 10 secs..." },
    321     { 1, 2, true,  true,  false, "Opening in 10 secs..." },
    322     { 0, 0, false, false, true,  "0 B, Paused" },
    323     { 1, 0, false, false, true,  "1 B, Paused" },
    324     { 0, 2, false, false, true,  "0/2 B, Paused" },
    325     { 1, 2, false, false, true,  "1/2 B, Paused" },
    326     { 0, 2, true,  false, true,  "0/2 B, Paused" },
    327     { 1, 2, true,  false, true,  "1/2 B, Paused" },
    328     { 0, 0, false, true,  true,  "0 B, Paused" },
    329     { 1, 0, false, true,  true,  "1 B, Paused" },
    330     { 0, 2, false, true,  true,  "0/2 B, Paused" },
    331     { 1, 2, false, true,  true,  "1/2 B, Paused" },
    332     { 0, 2, true,  true,  true,  "0/2 B, Paused" },
    333     { 1, 2, true,  true,  true,  "1/2 B, Paused" },
    334   };
    335 
    336   SetupDownloadItemDefaults();
    337 
    338   for (unsigned i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); i++) {
    339     const TestCase& test_case = kTestCases[i];
    340     Mock::VerifyAndClearExpectations(&item());
    341     Mock::VerifyAndClearExpectations(&model());
    342     EXPECT_CALL(item(), GetReceivedBytes())
    343         .WillRepeatedly(Return(test_case.received_bytes));
    344     EXPECT_CALL(item(), GetTotalBytes())
    345         .WillRepeatedly(Return(test_case.total_bytes));
    346     EXPECT_CALL(item(), TimeRemaining(_))
    347         .WillRepeatedly(testing::DoAll(
    348             testing::SetArgPointee<0>(base::TimeDelta::FromSeconds(10)),
    349             Return(test_case.time_remaining_known)));
    350     EXPECT_CALL(item(), GetOpenWhenComplete())
    351         .WillRepeatedly(Return(test_case.open_when_complete));
    352     EXPECT_CALL(item(), IsPaused())
    353         .WillRepeatedly(Return(test_case.is_paused));
    354 
    355     EXPECT_STREQ(test_case.expected_status,
    356                  base::UTF16ToUTF8(model().GetStatusText()).c_str());
    357   }
    358 }
    359 
    360 TEST_F(DownloadItemModelTest, ShouldShowInShelf) {
    361   SetupDownloadItemDefaults();
    362 
    363   // By default the download item should be displayable on the shelf.
    364   EXPECT_TRUE(model().ShouldShowInShelf());
    365 
    366   // Once explicitly set, ShouldShowInShelf() should return the explicit value.
    367   model().SetShouldShowInShelf(false);
    368   EXPECT_FALSE(model().ShouldShowInShelf());
    369 
    370   model().SetShouldShowInShelf(true);
    371   EXPECT_TRUE(model().ShouldShowInShelf());
    372 }
    373 
    374 TEST_F(DownloadItemModelTest, ShouldRemoveFromShelfWhenComplete) {
    375   const struct TestCase {
    376     DownloadItem::DownloadState state;
    377     bool is_dangerous;  // Expectation for IsDangerous().
    378     bool is_auto_open;  // Expectation for GetOpenWhenComplete().
    379     bool auto_opened;   // Whether the download was successfully
    380                         // auto-opened. Expecation for GetAutoOpened().
    381     bool expected_result;
    382   } kTestCases[] = {
    383     // All the valid combinations of state, is_dangerous, is_auto_open and
    384     // auto_opened.
    385     //
    386     //                              .--- Is dangerous.
    387     //                             |       .--- Auto open or temporary.
    388     //                             |      |      .--- Auto opened.
    389     //                             |      |      |      .--- Expected result.
    390     { DownloadItem::IN_PROGRESS, false, false, false, false},
    391     { DownloadItem::IN_PROGRESS, false, true , false, true },
    392     { DownloadItem::IN_PROGRESS, true , false, false, false},
    393     { DownloadItem::IN_PROGRESS, true , true , false, false},
    394     { DownloadItem::COMPLETE,    false, false, false, false},
    395     { DownloadItem::COMPLETE,    false, true , false, false},
    396     { DownloadItem::COMPLETE,    false, false, true , true },
    397     { DownloadItem::COMPLETE,    false, true , true , true },
    398     { DownloadItem::CANCELLED,   false, false, false, false},
    399     { DownloadItem::CANCELLED,   false, true , false, false},
    400     { DownloadItem::CANCELLED,   true , false, false, false},
    401     { DownloadItem::CANCELLED,   true , true , false, false},
    402     { DownloadItem::INTERRUPTED, false, false, false, false},
    403     { DownloadItem::INTERRUPTED, false, true , false, false},
    404     { DownloadItem::INTERRUPTED, true , false, false, false},
    405     { DownloadItem::INTERRUPTED, true , true , false, false}
    406   };
    407 
    408   SetupDownloadItemDefaults();
    409 
    410   for (unsigned i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); i++) {
    411     const TestCase& test_case = kTestCases[i];
    412     EXPECT_CALL(item(), GetOpenWhenComplete())
    413         .WillRepeatedly(Return(test_case.is_auto_open));
    414     EXPECT_CALL(item(), GetState())
    415         .WillRepeatedly(Return(test_case.state));
    416     EXPECT_CALL(item(), IsDangerous())
    417         .WillRepeatedly(Return(test_case.is_dangerous));
    418     EXPECT_CALL(item(), GetAutoOpened())
    419         .WillRepeatedly(Return(test_case.auto_opened));
    420 
    421     EXPECT_EQ(test_case.expected_result,
    422               model().ShouldRemoveFromShelfWhenComplete())
    423         << "Test case: " << i;
    424     Mock::VerifyAndClearExpectations(&item());
    425     Mock::VerifyAndClearExpectations(&model());
    426   }
    427 }
    428