2020#include < cstring>
2121#include < ctime>
2222#include < thread> // NOLINT
23+ #include < vector> // For std::vector in list tests
2324
2425#include " app_framework.h" // NOLINT
2526#include " firebase/app.h"
@@ -80,6 +81,9 @@ using app_framework::PathForResource;
8081using app_framework::ProcessEvents;
8182using firebase_test_framework::FirebaseTest;
8283using testing::ElementsAreArray;
84+ using testing::IsEmpty;
85+ using testing::UnorderedElementsAreArray;
86+
8387
8488class FirebaseStorageTest : public FirebaseTest {
8589 public:
@@ -96,8 +100,10 @@ class FirebaseStorageTest : public FirebaseTest {
96100 // Called after each test.
97101 void TearDown () override ;
98102
99- // File references that we need to delete on test exit.
100103 protected:
104+ // Root reference for list tests.
105+ firebase::storage::StorageReference list_test_root_;
106+
101107 // Initialize Firebase App and Firebase Auth.
102108 static void InitializeAppAndAuth ();
103109 // Shut down Firebase App and Firebase Auth.
@@ -118,6 +124,18 @@ class FirebaseStorageTest : public FirebaseTest {
118124 // Create a unique working folder and return a reference to it.
119125 firebase::storage::StorageReference CreateFolder ();
120126
127+ // Uploads a string as a file to the given StorageReference.
128+ void UploadStringAsFile (
129+ firebase::storage::StorageReference& ref, const std::string& content,
130+ const char * content_type = nullptr );
131+
132+ // Verifies the contents of a ListResult.
133+ void VerifyListResultContains (
134+ const firebase::storage::ListResult& list_result,
135+ const std::vector<std::string>& expected_item_names,
136+ const std::vector<std::string>& expected_prefix_names);
137+
138+
121139 static firebase::App* shared_app_;
122140 static firebase::auth::Auth* shared_auth_;
123141
@@ -212,6 +230,16 @@ void FirebaseStorageTest::TerminateAppAndAuth() {
212230void FirebaseStorageTest::SetUp () {
213231 FirebaseTest::SetUp ();
214232 InitializeStorage ();
233+ if (storage_ != nullptr && storage_->GetReference ().is_valid ()) {
234+ list_test_root_ = CreateFolder ().Child (" list_tests_root" );
235+ // list_test_root_ itself doesn't need to be in cleanup_files_ if its parent from CreateFolder() is.
236+ // However, specific files/folders created under list_test_root_ for each test *will* be added
237+ // via UploadStringAsFile or by explicitly adding the parent of a set of files for that test.
238+ } else {
239+ // Handle cases where storage might not be initialized (e.g. if InitializeStorage fails)
240+ // by providing a default, invalid reference.
241+ list_test_root_ = firebase::storage::StorageReference ();
242+ }
215243}
216244
217245void FirebaseStorageTest::TearDown () {
@@ -313,6 +341,62 @@ void FirebaseStorageTest::SignOut() {
313341 EXPECT_FALSE (shared_auth_->current_user ().is_valid ());
314342}
315343
344+ void FirebaseStorageTest::UploadStringAsFile (
345+ firebase::storage::StorageReference& ref, const std::string& content,
346+ const char * content_type) {
347+ LogDebug (" Uploading string content to: gs://%s%s" , ref.bucket ().c_str (),
348+ ref.full_path ().c_str ());
349+ firebase::storage::Metadata metadata;
350+ if (content_type) {
351+ metadata.set_content_type (content_type);
352+ }
353+ firebase::Future<firebase::storage::Metadata> future =
354+ RunWithRetry<firebase::storage::Metadata>(
355+ [&]() { return ref.PutBytes (content.c_str (), content.length (), metadata); });
356+ WaitForCompletion (future, " UploadStringAsFile" );
357+ ASSERT_EQ (future.error (), firebase::storage::kErrorNone )
358+ << " Failed to upload to " << ref.full_path () << " : "
359+ << future.error_message ();
360+ ASSERT_NE (future.result (), nullptr );
361+ // On some platforms (iOS), size_bytes might not be immediately available or might be 0
362+ // if the upload was very fast and metadata propagation is slow.
363+ // For small files, this is less critical than the content being there.
364+ // For larger files in other tests, size_bytes is asserted.
365+ // ASSERT_EQ(future.result()->size_bytes(), content.length());
366+ cleanup_files_.push_back (ref);
367+ }
368+
369+ void FirebaseStorageTest::VerifyListResultContains (
370+ const firebase::storage::ListResult& list_result,
371+ const std::vector<std::string>& expected_item_names,
372+ const std::vector<std::string>& expected_prefix_names) {
373+ ASSERT_TRUE (list_result.is_valid ());
374+
375+ std::vector<std::string> actual_item_names;
376+ for (const auto & item_ref : list_result.items ()) {
377+ actual_item_names.push_back (item_ref.name ());
378+ }
379+ std::sort (actual_item_names.begin (), actual_item_names.end ());
380+ std::vector<std::string> sorted_expected_item_names = expected_item_names;
381+ std::sort (sorted_expected_item_names.begin (), sorted_expected_item_names.end ());
382+
383+ EXPECT_THAT (actual_item_names, ::testing::ContainerEq (sorted_expected_item_names))
384+ << " Item names do not match expected." ;
385+
386+
387+ std::vector<std::string> actual_prefix_names;
388+ for (const auto & prefix_ref : list_result.prefixes ()) {
389+ actual_prefix_names.push_back (prefix_ref.name ());
390+ }
391+ std::sort (actual_prefix_names.begin (), actual_prefix_names.end ());
392+ std::vector<std::string> sorted_expected_prefix_names = expected_prefix_names;
393+ std::sort (sorted_expected_prefix_names.begin (), sorted_expected_prefix_names.end ());
394+
395+ EXPECT_THAT (actual_prefix_names, ::testing::ContainerEq (sorted_expected_prefix_names))
396+ << " Prefix names do not match expected." ;
397+ }
398+
399+
316400firebase::storage::StorageReference FirebaseStorageTest::CreateFolder () {
317401 // Generate a folder for the test data based on the time in milliseconds.
318402 int64_t time_in_microseconds = GetCurrentTimeInMicroseconds ();
@@ -1622,4 +1706,200 @@ TEST_F(FirebaseStorageTest, TestInvalidatingReferencesWhenDeletingApp) {
16221706 InitializeAppAndAuth ();
16231707}
16241708
1709+ TEST_F (FirebaseStorageTest, ListAllBasic) {
1710+ SKIP_TEST_ON_ANDROID_EMULATOR; // List tests can be slow on emulators or have quota issues.
1711+ SignIn ();
1712+ ASSERT_TRUE (list_test_root_.is_valid ()) << " List test root is not valid." ;
1713+
1714+ firebase::storage::StorageReference list_all_base =
1715+ list_test_root_.Child (" list_all_basic_test" );
1716+ // cleanup_files_.push_back(list_all_base); // Not a file, its contents are files.
1717+
1718+ UploadStringAsFile (list_all_base.Child (" file_a.txt" ), " content_a" );
1719+ UploadStringAsFile (list_all_base.Child (" file_b.txt" ), " content_b" );
1720+ UploadStringAsFile (list_all_base.Child (" prefix1/file_c.txt" ), " content_c_in_prefix1" );
1721+ UploadStringAsFile (list_all_base.Child (" prefix2/file_e.txt" ), " content_e_in_prefix2" );
1722+
1723+ LogDebug (" Calling ListAll() on gs://%s%s" , list_all_base.bucket ().c_str (),
1724+ list_all_base.full_path ().c_str ());
1725+ firebase::Future<firebase::storage::ListResult> future =
1726+ list_all_base.ListAll ();
1727+ WaitForCompletion (future, " ListAllBasic" );
1728+
1729+ ASSERT_EQ (future.error (), firebase::storage::kErrorNone )
1730+ << future.error_message ();
1731+ ASSERT_NE (future.result (), nullptr );
1732+ const firebase::storage::ListResult* result = future.result ();
1733+
1734+ VerifyListResultContains (*result, {" file_a.txt" , " file_b.txt" },
1735+ {" prefix1/" , " prefix2/" });
1736+ EXPECT_TRUE (result->page_token ().empty ()) << " Page token should be empty for ListAll." ;
1737+ }
1738+
1739+ TEST_F (FirebaseStorageTest, ListPaginated) {
1740+ SKIP_TEST_ON_ANDROID_EMULATOR;
1741+ SignIn ();
1742+ ASSERT_TRUE (list_test_root_.is_valid ()) << " List test root is not valid." ;
1743+
1744+ firebase::storage::StorageReference list_paginated_base =
1745+ list_test_root_.Child (" list_paginated_test" );
1746+ // cleanup_files_.push_back(list_paginated_base);
1747+
1748+ // Expected total entries: file_aa.txt, file_bb.txt, file_ee.txt, prefix_x/, prefix_y/ (5 entries)
1749+ UploadStringAsFile (list_paginated_base.Child (" file_aa.txt" ), " content_aa" );
1750+ UploadStringAsFile (list_paginated_base.Child (" prefix_x/file_cc.txt" ), " content_cc_in_prefix_x" );
1751+ UploadStringAsFile (list_paginated_base.Child (" file_bb.txt" ), " content_bb" );
1752+ UploadStringAsFile (list_paginated_base.Child (" prefix_y/file_dd.txt" ), " content_dd_in_prefix_y" );
1753+ UploadStringAsFile (list_paginated_base.Child (" file_ee.txt" ), " content_ee" );
1754+
1755+
1756+ std::vector<std::string> all_item_names_collected;
1757+ std::vector<std::string> all_prefix_names_collected;
1758+ std::string page_token = " " ;
1759+ const int page_size = 2 ;
1760+ int page_count = 0 ;
1761+ const int max_pages = 5 ; // Safety break for loop
1762+
1763+ LogDebug (" Starting paginated List() on gs://%s%s with page_size %d" ,
1764+ list_paginated_base.bucket ().c_str (), list_paginated_base.full_path ().c_str (), page_size);
1765+
1766+ do {
1767+ page_count++;
1768+ LogDebug (" Fetching page %d, token: '%s'" , page_count, page_token.c_str ());
1769+ firebase::Future<firebase::storage::ListResult> future =
1770+ page_token.empty () ? list_paginated_base.List (page_size)
1771+ : list_paginated_base.List (page_size, page_token.c_str ());
1772+ WaitForCompletion (future, " ListPaginated - Page " + std::to_string (page_count));
1773+
1774+ ASSERT_EQ (future.error (), firebase::storage::kErrorNone ) << future.error_message ();
1775+ ASSERT_NE (future.result (), nullptr );
1776+ const firebase::storage::ListResult* result = future.result ();
1777+ ASSERT_TRUE (result->is_valid ());
1778+
1779+ LogDebug (" Page %d items: %zu, prefixes: %zu" , page_count, result->items ().size (), result->prefixes ().size ());
1780+ for (const auto & item : result->items ()) {
1781+ all_item_names_collected.push_back (item.name ());
1782+ LogDebug (" Item: %s" , item.name ().c_str ());
1783+ }
1784+ for (const auto & prefix : result->prefixes ()) {
1785+ all_prefix_names_collected.push_back (prefix.name ());
1786+ LogDebug (" Prefix: %s" , prefix.name ().c_str ());
1787+ }
1788+
1789+ page_token = result->page_token ();
1790+
1791+ size_t entries_on_page = result->items ().size () + result->prefixes ().size ();
1792+
1793+ if (!page_token.empty ()) {
1794+ EXPECT_EQ (entries_on_page, page_size) << " A non-last page should have full page_size entries." ;
1795+ } else {
1796+ // This is the last page
1797+ size_t total_entries = 5 ;
1798+ size_t expected_entries_on_last_page = total_entries % page_size;
1799+ if (expected_entries_on_last_page == 0 && total_entries > 0 ) { // if total is a multiple of page_size
1800+ expected_entries_on_last_page = page_size;
1801+ }
1802+ EXPECT_EQ (entries_on_page, expected_entries_on_last_page);
1803+ }
1804+ } while (!page_token.empty () && page_count < max_pages);
1805+
1806+ EXPECT_LT (page_count, max_pages) << " Exceeded max_pages, possible infinite loop." ;
1807+ EXPECT_EQ (page_count, (5 + page_size -1 ) / page_size) << " Unexpected number of pages." ;
1808+
1809+
1810+ std::vector<std::string> expected_final_items = {" file_aa.txt" , " file_bb.txt" , " file_ee.txt" };
1811+ std::vector<std::string> expected_final_prefixes = {" prefix_x/" , " prefix_y/" };
1812+
1813+ // VerifyListResultContains needs a ListResult object. We can't directly use it with collected names.
1814+ // Instead, we sort and compare the collected names.
1815+ std::sort (all_item_names_collected.begin (), all_item_names_collected.end ());
1816+ std::sort (all_prefix_names_collected.begin (), 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, ::testing::ContainerEq (expected_final_items));
1821+ EXPECT_THAT (all_prefix_names_collected, ::testing::ContainerEq (expected_final_prefixes));
1822+ }
1823+
1824+
1825+ TEST_F (FirebaseStorageTest, ListEmpty) {
1826+ SKIP_TEST_ON_ANDROID_EMULATOR;
1827+ SignIn ();
1828+ ASSERT_TRUE (list_test_root_.is_valid ()) << " List test root is not valid." ;
1829+
1830+ firebase::storage::StorageReference list_empty_ref =
1831+ list_test_root_.Child (" list_empty_folder_test" );
1832+ // Do not upload anything to this reference.
1833+ // cleanup_files_.push_back(list_empty_ref); // Not a file
1834+
1835+ LogDebug (" Calling ListAll() on empty folder: gs://%s%s" ,
1836+ list_empty_ref.bucket ().c_str (), list_empty_ref.full_path ().c_str ());
1837+ firebase::Future<firebase::storage::ListResult> future =
1838+ list_empty_ref.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;
1852+ SignIn ();
1853+ ASSERT_TRUE (list_test_root_.is_valid ()) << " List test root is not valid." ;
1854+
1855+ firebase::storage::StorageReference list_max_greater_base =
1856+ list_test_root_.Child (" list_max_greater_test" );
1857+ // cleanup_files_.push_back(list_max_greater_base);
1858+
1859+ UploadStringAsFile (list_max_greater_base.Child (" only_file.txt" ), " content_only" );
1860+ UploadStringAsFile (list_max_greater_base.Child (" only_prefix/another.txt" ), " content_another_in_prefix" );
1861+
1862+ LogDebug (" Calling List(10) on gs://%s%s" ,
1863+ list_max_greater_base.bucket ().c_str (),
1864+ list_max_greater_base.full_path ().c_str ());
1865+ firebase::Future<firebase::storage::ListResult> future =
1866+ list_max_greater_base.List (10 ); // Max results (10) > actual (1 file + 1 prefix = 2)
1867+ WaitForCompletion (future, " ListWithMaxResultsGreaterThanActual" );
1868+
1869+ ASSERT_EQ (future.error (), firebase::storage::kErrorNone )
1870+ << future.error_message ();
1871+ ASSERT_NE (future.result (), nullptr );
1872+ const firebase::storage::ListResult* result = future.result ();
1873+
1874+ VerifyListResultContains (*result, {" only_file.txt" }, {" only_prefix/" });
1875+ EXPECT_TRUE (result->page_token ().empty ());
1876+ }
1877+
1878+ TEST_F (FirebaseStorageTest, ListNonExistentPath) {
1879+ SKIP_TEST_ON_ANDROID_EMULATOR;
1880+ SignIn ();
1881+ ASSERT_TRUE (list_test_root_.is_valid ()) << " List test root is not valid." ;
1882+
1883+ firebase::storage::StorageReference list_non_existent_ref =
1884+ list_test_root_.Child (" this_folder_does_not_exist_for_list_test" );
1885+ // No cleanup needed as nothing is created.
1886+
1887+ LogDebug (" Calling ListAll() on non-existent path: gs://%s%s" ,
1888+ list_non_existent_ref.bucket ().c_str (),
1889+ list_non_existent_ref.full_path ().c_str ());
1890+ firebase::Future<firebase::storage::ListResult> future =
1891+ list_non_existent_ref.ListAll ();
1892+ WaitForCompletion (future, " ListNonExistentPath" );
1893+
1894+ // Listing a non-existent path should not be an error, it's just an empty list.
1895+ ASSERT_EQ (future.error (), firebase::storage::kErrorNone )
1896+ << future.error_message ();
1897+ ASSERT_NE (future.result (), nullptr );
1898+ const firebase::storage::ListResult* result = future.result ();
1899+
1900+ VerifyListResultContains (*result, {}, {});
1901+ EXPECT_TRUE (result->page_token ().empty ());
1902+ }
1903+
1904+
16251905} // namespace firebase_testapp_automated
0 commit comments