20
20
#include < cstring>
21
21
#include < ctime>
22
22
#include < thread> // NOLINT
23
+ #include < vector> // For std::vector in list tests
23
24
24
25
#include " app_framework.h" // NOLINT
25
26
#include " firebase/app.h"
@@ -80,6 +81,8 @@ using app_framework::PathForResource;
80
81
using app_framework::ProcessEvents;
81
82
using firebase_test_framework::FirebaseTest;
82
83
using testing::ElementsAreArray;
84
+ using testing::IsEmpty;
85
+ using testing::UnorderedElementsAreArray;
83
86
84
87
class FirebaseStorageTest : public FirebaseTest {
85
88
public:
@@ -96,7 +99,6 @@ class FirebaseStorageTest : public FirebaseTest {
96
99
// Called after each test.
97
100
void TearDown () override ;
98
101
99
- // File references that we need to delete on test exit.
100
102
protected:
101
103
// Initialize Firebase App and Firebase Auth.
102
104
static void InitializeAppAndAuth ();
@@ -118,6 +120,17 @@ class FirebaseStorageTest : public FirebaseTest {
118
120
// Create a unique working folder and return a reference to it.
119
121
firebase::storage::StorageReference CreateFolder ();
120
122
123
+ // Uploads a string as a file to the given StorageReference.
124
+ void UploadStringAsFile (firebase::storage::StorageReference& ref,
125
+ const std::string& content,
126
+ const char * content_type = nullptr );
127
+
128
+ // Verifies the contents of a ListResult.
129
+ void VerifyListResultContains (
130
+ const firebase::storage::ListResult& list_result,
131
+ const std::vector<std::string>& expected_item_names,
132
+ const std::vector<std::string>& expected_prefix_names);
133
+
121
134
static firebase::App* shared_app_;
122
135
static firebase::auth::Auth* shared_auth_;
123
136
@@ -212,6 +225,7 @@ void FirebaseStorageTest::TerminateAppAndAuth() {
212
225
void FirebaseStorageTest::SetUp () {
213
226
FirebaseTest::SetUp ();
214
227
InitializeStorage ();
228
+ // list_test_root_ removed from SetUp
215
229
}
216
230
217
231
void FirebaseStorageTest::TearDown () {
@@ -313,6 +327,65 @@ void FirebaseStorageTest::SignOut() {
313
327
EXPECT_FALSE (shared_auth_->current_user ().is_valid ());
314
328
}
315
329
330
+ void FirebaseStorageTest::UploadStringAsFile (
331
+ firebase::storage::StorageReference& ref, const std::string& content,
332
+ const char * content_type) {
333
+ LogDebug (" Uploading string content to: gs://%s%s" , ref.bucket ().c_str (),
334
+ ref.full_path ().c_str ());
335
+ firebase::storage::Metadata metadata;
336
+ if (content_type) {
337
+ metadata.set_content_type (content_type);
338
+ }
339
+ firebase::Future<firebase::storage::Metadata> future =
340
+ RunWithRetry<firebase::storage::Metadata>([&]() {
341
+ return ref.PutBytes (content.c_str (), content.length (), metadata);
342
+ });
343
+ WaitForCompletion (future, " UploadStringAsFile" );
344
+ ASSERT_EQ (future.error (), firebase::storage::kErrorNone )
345
+ << " Failed to upload to " << ref.full_path () << " : "
346
+ << future.error_message ();
347
+ ASSERT_NE (future.result (), nullptr );
348
+ // On some platforms (iOS), size_bytes might not be immediately available or
349
+ // might be 0 if the upload was very fast and metadata propagation is slow.
350
+ // For small files, this is less critical than the content being there.
351
+ // For larger files in other tests, size_bytes is asserted.
352
+ // ASSERT_EQ(future.result()->size_bytes(), content.length());
353
+ cleanup_files_.push_back (ref);
354
+ }
355
+
356
+ void FirebaseStorageTest::VerifyListResultContains (
357
+ const firebase::storage::ListResult& list_result,
358
+ const std::vector<std::string>& expected_item_names,
359
+ const std::vector<std::string>& expected_prefix_names) {
360
+ ASSERT_TRUE (list_result.is_valid ());
361
+
362
+ std::vector<std::string> actual_item_names;
363
+ for (const auto & item_ref : list_result.items ()) {
364
+ actual_item_names.push_back (item_ref.name ());
365
+ }
366
+ std::sort (actual_item_names.begin (), actual_item_names.end ());
367
+ std::vector<std::string> sorted_expected_item_names = expected_item_names;
368
+ std::sort (sorted_expected_item_names.begin (),
369
+ sorted_expected_item_names.end ());
370
+
371
+ EXPECT_THAT (actual_item_names,
372
+ ::testing::ContainerEq (sorted_expected_item_names))
373
+ << "Item names do not match expected.";
374
+
375
+ std::vector<std::string> actual_prefix_names;
376
+ for (const auto & prefix_ref : list_result.prefixes ()) {
377
+ actual_prefix_names.push_back (prefix_ref.name ());
378
+ }
379
+ std::sort (actual_prefix_names.begin (), actual_prefix_names.end ());
380
+ std::vector<std::string> sorted_expected_prefix_names = expected_prefix_names;
381
+ std::sort (sorted_expected_prefix_names.begin (),
382
+ sorted_expected_prefix_names.end ());
383
+
384
+ EXPECT_THAT (actual_prefix_names,
385
+ ::testing::ContainerEq (sorted_expected_prefix_names))
386
+ << "Prefix names do not match expected.";
387
+ }
388
+
316
389
firebase::storage::StorageReference FirebaseStorageTest::CreateFolder () {
317
390
// Generate a folder for the test data based on the time in milliseconds.
318
391
int64_t time_in_microseconds = GetCurrentTimeInMicroseconds ();
@@ -1622,4 +1695,213 @@ TEST_F(FirebaseStorageTest, TestInvalidatingReferencesWhenDeletingApp) {
1622
1695
InitializeAppAndAuth ();
1623
1696
}
1624
1697
1698
+ TEST_F (FirebaseStorageTest, ListAllBasic) {
1699
+ // SKIP_TEST_ON_ANDROID_EMULATOR; // Removed
1700
+ SignIn ();
1701
+ firebase::storage::StorageReference test_root =
1702
+ CreateFolder ().Child (" list_all_basic_root" );
1703
+ ASSERT_TRUE (test_root.is_valid ())
1704
+ << " Test root for ListAllBasic is not valid." ;
1705
+
1706
+ UploadStringAsFile (test_root.Child (" file_a.txt" ), " content_a" );
1707
+ UploadStringAsFile (test_root.Child (" file_b.txt" ), " content_b" );
1708
+ UploadStringAsFile (test_root.Child (" prefix1/file_c.txt" ),
1709
+ " content_c_in_prefix1" );
1710
+ UploadStringAsFile (test_root.Child (" prefix2/file_e.txt" ),
1711
+ " content_e_in_prefix2" );
1712
+
1713
+ LogDebug (" Calling ListAll() on gs://%s%s" , test_root.bucket ().c_str (),
1714
+ test_root.full_path ().c_str ());
1715
+ firebase::Future<firebase::storage::ListResult> future = test_root.ListAll ();
1716
+ WaitForCompletion (future, " ListAllBasic" );
1717
+
1718
+ ASSERT_EQ (future.error (), firebase::storage::kErrorNone )
1719
+ << future.error_message ();
1720
+ ASSERT_NE (future.result (), nullptr );
1721
+ const firebase::storage::ListResult* result = future.result ();
1722
+
1723
+ VerifyListResultContains (*result, {" file_a.txt" , " file_b.txt" },
1724
+ {" prefix1/" , " prefix2/" });
1725
+ EXPECT_TRUE (result->page_token ().empty ())
1726
+ << " Page token should be empty for ListAll." ;
1727
+ }
1728
+
1729
+ TEST_F (FirebaseStorageTest, ListPaginated) {
1730
+ // SKIP_TEST_ON_ANDROID_EMULATOR; // Removed
1731
+ SignIn ();
1732
+ firebase::storage::StorageReference test_root =
1733
+ CreateFolder ().Child (" list_paginated_root" );
1734
+ ASSERT_TRUE (test_root.is_valid ())
1735
+ << " Test root for ListPaginated is not valid." ;
1736
+
1737
+ // Expected total entries: file_aa.txt, file_bb.txt, file_ee.txt, prefix_x/,
1738
+ // prefix_y/ (5 entries)
1739
+ UploadStringAsFile (test_root.Child (" file_aa.txt" ), " content_aa" );
1740
+ UploadStringAsFile (test_root.Child (" prefix_x/file_cc.txt" ),
1741
+ " content_cc_in_prefix_x" );
1742
+ UploadStringAsFile (test_root.Child (" file_bb.txt" ), " content_bb" );
1743
+ UploadStringAsFile (test_root.Child (" prefix_y/file_dd.txt" ),
1744
+ " content_dd_in_prefix_y" );
1745
+ UploadStringAsFile (test_root.Child (" file_ee.txt" ), " content_ee" );
1746
+
1747
+ std::vector<std::string> all_item_names_collected;
1748
+ std::vector<std::string> all_prefix_names_collected;
1749
+ std::string page_token = " " ;
1750
+ const int page_size = 2 ;
1751
+ int page_count = 0 ;
1752
+ const int max_pages = 5 ; // Safety break for loop
1753
+
1754
+ LogDebug (" Starting paginated List() on gs://%s%s with page_size %d" ,
1755
+ test_root.bucket ().c_str (), test_root.full_path ().c_str (),
1756
+ page_size);
1757
+
1758
+ do {
1759
+ page_count++;
1760
+ LogDebug (" Fetching page %d, token: '%s'" , page_count, page_token.c_str ());
1761
+ firebase::Future<firebase::storage::ListResult> future =
1762
+ page_token.empty () ? test_root.List (page_size)
1763
+ : test_root.List (page_size, page_token.c_str ());
1764
+ WaitForCompletion (future,
1765
+ " ListPaginated - Page " + std::to_string (page_count));
1766
+
1767
+ ASSERT_EQ (future.error (), firebase::storage::kErrorNone )
1768
+ << future.error_message ();
1769
+ ASSERT_NE (future.result (), nullptr );
1770
+ const firebase::storage::ListResult* result = future.result ();
1771
+ ASSERT_TRUE (result->is_valid ());
1772
+
1773
+ LogDebug (" Page %d items: %zu, prefixes: %zu" , page_count,
1774
+ result->items ().size (), result->prefixes ().size ());
1775
+ for (const auto & item : result->items ()) {
1776
+ all_item_names_collected.push_back (item.name ());
1777
+ LogDebug (" Item: %s" , item.name ().c_str ());
1778
+ }
1779
+ for (const auto & prefix : result->prefixes ()) {
1780
+ all_prefix_names_collected.push_back (prefix.name ());
1781
+ LogDebug (" Prefix: %s" , prefix.name ().c_str ());
1782
+ }
1783
+
1784
+ page_token = result->page_token ();
1785
+
1786
+ size_t entries_on_page = result->items ().size () + result->prefixes ().size ();
1787
+
1788
+ if (!page_token.empty ()) {
1789
+ EXPECT_EQ (entries_on_page, page_size)
1790
+ << " A non-last page should have full page_size entries." ;
1791
+ } else {
1792
+ // This is the last page
1793
+ size_t total_entries = 5 ;
1794
+ size_t expected_entries_on_last_page = total_entries % page_size;
1795
+ if (expected_entries_on_last_page == 0 &&
1796
+ total_entries > 0 ) { // if total is a multiple of page_size
1797
+ expected_entries_on_last_page = page_size;
1798
+ }
1799
+ EXPECT_EQ (entries_on_page, expected_entries_on_last_page);
1800
+ }
1801
+ } while (!page_token.empty () && page_count < max_pages);
1802
+
1803
+ EXPECT_LT (page_count, max_pages)
1804
+ << " Exceeded max_pages, possible infinite loop." ;
1805
+ EXPECT_EQ (page_count, (5 + page_size - 1 ) / page_size)
1806
+ << " Unexpected number of pages." ;
1807
+
1808
+ std::vector<std::string> expected_final_items = {" file_aa.txt" , " file_bb.txt" ,
1809
+ " file_ee.txt" };
1810
+ std::vector<std::string> expected_final_prefixes = {" prefix_x/" , " prefix_y/" };
1811
+
1812
+ // VerifyListResultContains needs a ListResult object. We can't directly use
1813
+ // it with collected names. Instead, we sort and compare the collected names.
1814
+ std::sort (all_item_names_collected.begin (), all_item_names_collected.end ());
1815
+ std::sort (all_prefix_names_collected.begin (),
1816
+ all_prefix_names_collected.end ());
1817
+ std::sort (expected_final_items.begin (), expected_final_items.end ());
1818
+ std::sort (expected_final_prefixes.begin (), expected_final_prefixes.end ());
1819
+
1820
+ EXPECT_THAT (all_item_names_collected,
1821
+ ::testing::ContainerEq (expected_final_items));
1822
+ EXPECT_THAT (all_prefix_names_collected,
1823
+ ::testing::ContainerEq (expected_final_prefixes));
1824
+ }
1825
+
1826
+ TEST_F (FirebaseStorageTest, ListEmpty) {
1827
+ // SKIP_TEST_ON_ANDROID_EMULATOR; // No skip needed as it's a lightweight
1828
+ // test.
1829
+ SignIn ();
1830
+ firebase::storage::StorageReference test_root =
1831
+ CreateFolder ().Child (" list_empty_root" );
1832
+ ASSERT_TRUE (test_root.is_valid ()) << " Test root for ListEmpty is not valid." ;
1833
+
1834
+ // Do not upload anything to test_root.
1835
+
1836
+ LogDebug (" Calling ListAll() on empty folder: gs://%s%s" ,
1837
+ test_root.bucket ().c_str (), test_root.full_path ().c_str ());
1838
+ firebase::Future<firebase::storage::ListResult> future = test_root.ListAll ();
1839
+ WaitForCompletion (future, " ListEmpty" );
1840
+
1841
+ ASSERT_EQ (future.error (), firebase::storage::kErrorNone )
1842
+ << future.error_message ();
1843
+ ASSERT_NE (future.result (), nullptr );
1844
+ const firebase::storage::ListResult* result = future.result ();
1845
+
1846
+ VerifyListResultContains (*result, {}, {});
1847
+ EXPECT_TRUE (result->page_token ().empty ());
1848
+ }
1849
+
1850
+ TEST_F (FirebaseStorageTest, ListWithMaxResultsGreaterThanActual) {
1851
+ // SKIP_TEST_ON_ANDROID_EMULATOR; // No skip needed.
1852
+ SignIn ();
1853
+ firebase::storage::StorageReference test_root =
1854
+ CreateFolder ().Child (" list_max_greater_root" );
1855
+ ASSERT_TRUE (test_root.is_valid ())
1856
+ << " Test root for ListWithMaxResultsGreaterThanActual is not valid." ;
1857
+
1858
+ UploadStringAsFile (test_root.Child (" only_file.txt" ), " content_only" );
1859
+ UploadStringAsFile (test_root.Child (" only_prefix/another.txt" ),
1860
+ " content_another_in_prefix" );
1861
+
1862
+ LogDebug (" Calling List(10) on gs://%s%s" , test_root.bucket ().c_str (),
1863
+ test_root.full_path ().c_str ());
1864
+ firebase::Future<firebase::storage::ListResult> future =
1865
+ test_root.List (10 ); // Max results (10) > actual (1 file + 1 prefix = 2)
1866
+ WaitForCompletion (future, " ListWithMaxResultsGreaterThanActual" );
1867
+
1868
+ ASSERT_EQ (future.error (), firebase::storage::kErrorNone )
1869
+ << future.error_message ();
1870
+ ASSERT_NE (future.result (), nullptr );
1871
+ const firebase::storage::ListResult* result = future.result ();
1872
+
1873
+ VerifyListResultContains (*result, {" only_file.txt" }, {" only_prefix/" });
1874
+ EXPECT_TRUE (result->page_token ().empty ());
1875
+ }
1876
+
1877
+ TEST_F (FirebaseStorageTest, ListNonExistentPath) {
1878
+ // SKIP_TEST_ON_ANDROID_EMULATOR; // No skip needed.
1879
+ SignIn ();
1880
+ firebase::storage::StorageReference test_root =
1881
+ CreateFolder ().Child (" list_non_existent_parent_root" );
1882
+ ASSERT_TRUE (test_root.is_valid ())
1883
+ << " Test root for ListNonExistentPath is not valid." ;
1884
+
1885
+ firebase::storage::StorageReference non_existent_ref =
1886
+ test_root.Child (" this_folder_truly_does_not_exist" );
1887
+ // No cleanup needed as nothing is created.
1888
+
1889
+ LogDebug (" Calling ListAll() on non-existent path: gs://%s%s" ,
1890
+ non_existent_ref.bucket ().c_str (),
1891
+ non_existent_ref.full_path ().c_str ());
1892
+ firebase::Future<firebase::storage::ListResult> future =
1893
+ non_existent_ref.ListAll ();
1894
+ WaitForCompletion (future, " ListNonExistentPath" );
1895
+
1896
+ // Listing a non-existent path should not be an error, it's just an empty
1897
+ // list.
1898
+ ASSERT_EQ (future.error (), firebase::storage::kErrorNone )
1899
+ << future.error_message ();
1900
+ ASSERT_NE (future.result (), nullptr );
1901
+ const firebase::storage::ListResult* result = future.result ();
1902
+
1903
+ VerifyListResultContains (*result, {}, {});
1904
+ EXPECT_TRUE (result->page_token ().empty ());
1905
+ }
1906
+
1625
1907
} // namespace firebase_testapp_automated
0 commit comments