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,8 @@ using app_framework::PathForResource;
8081using app_framework::ProcessEvents;
8182using firebase_test_framework::FirebaseTest;
8283using testing::ElementsAreArray;
84+ using testing::IsEmpty;
85+ using testing::UnorderedElementsAreArray;
8386
8487class FirebaseStorageTest : public FirebaseTest {
8588 public:
@@ -96,7 +99,6 @@ class FirebaseStorageTest : public FirebaseTest {
9699 // Called after each test.
97100 void TearDown () override ;
98101
99- // File references that we need to delete on test exit.
100102 protected:
101103 // Initialize Firebase App and Firebase Auth.
102104 static void InitializeAppAndAuth ();
@@ -118,6 +120,17 @@ class FirebaseStorageTest : public FirebaseTest {
118120 // Create a unique working folder and return a reference to it.
119121 firebase::storage::StorageReference CreateFolder ();
120122
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+
121134 static firebase::App* shared_app_;
122135 static firebase::auth::Auth* shared_auth_;
123136
@@ -212,6 +225,7 @@ void FirebaseStorageTest::TerminateAppAndAuth() {
212225void FirebaseStorageTest::SetUp () {
213226 FirebaseTest::SetUp ();
214227 InitializeStorage ();
228+ // list_test_root_ removed from SetUp
215229}
216230
217231void FirebaseStorageTest::TearDown () {
@@ -313,6 +327,65 @@ void FirebaseStorageTest::SignOut() {
313327 EXPECT_FALSE (shared_auth_->current_user ().is_valid ());
314328}
315329
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+
316389firebase::storage::StorageReference FirebaseStorageTest::CreateFolder () {
317390 // Generate a folder for the test data based on the time in milliseconds.
318391 int64_t time_in_microseconds = GetCurrentTimeInMicroseconds ();
@@ -1622,4 +1695,213 @@ TEST_F(FirebaseStorageTest, TestInvalidatingReferencesWhenDeletingApp) {
16221695 InitializeAppAndAuth ();
16231696}
16241697
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+
16251907} // namespace firebase_testapp_automated
0 commit comments