From 7413a743be1eaa053d855fe20831c4c92902a320 Mon Sep 17 00:00:00 2001 From: dlarocque Date: Tue, 19 May 2026 12:59:32 -0400 Subject: [PATCH 1/9] feat(firestore): add support for 16MB docs --- .../LargeDocIntegrationTests.swift | 145 ++++++++++++++++++ Firestore/core/src/remote/grpc_connection.cc | 5 + 2 files changed, 150 insertions(+) create mode 100644 Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift diff --git a/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift b/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift new file mode 100644 index 00000000000..98bb1219e27 --- /dev/null +++ b/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift @@ -0,0 +1,145 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import FirebaseFirestore +import Foundation +import XCTest + +@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) +final class LargeDocIntegrationTests: FSTIntegrationTestCase { + // Test data prerequisites populated by the nightly seeder script. + let SEED_COLLECTION = "serverSdkTests" + let DOC_15_9MB_UNICODE = "doc_15_9MB_unicode" + let COL_LARGE_DOCS = "col_large_docs" + + override func setUp() async throws { + try await super.setUp() + + // Skip tests if the backend edition is not supported or if not nightly + if FSTIntegrationTestCase.targetBackend() != .nightly || FSTIntegrationTestCase + .backendEdition() == .standard { + throw XCTSkip("Skipping large document tests because backend is not compatible.") + } + } + + // MARK: - Helper Methods + + private func generateString(sizeInBytes: Int) -> String { + return String(repeating: "a", count: sizeInBytes) + } + + override func collectionRef() -> CollectionReference { + return db.collection(SEED_COLLECTION) + } + + // MARK: - Test Cases + + func testReadAndCacheLargeUnicodeDocument() async throws { + let docRef = collectionRef().document(DOC_15_9MB_UNICODE) + defer { Task { try? await db.enableNetwork() } } + + let serverSnapshot = try await docRef.getDocument(source: .server) + XCTAssertTrue(serverSnapshot.exists) + + try await db.disableNetwork() + + let cacheSnapshot = try await docRef.getDocument(source: .cache) + XCTAssertTrue(cacheSnapshot.exists) + + let serverData = serverSnapshot.data() as NSDictionary? + let cacheData = cacheSnapshot.data() as NSDictionary? + XCTAssertEqual(serverData, cacheData) + } + + func testQueryLargeDocumentsForcesLocalScan() async throws { + let colRef = db.collection(COL_LARGE_DOCS) + defer { Task { try? await db.enableNetwork() } } + + // Populate cache + _ = try await colRef.document("doc_a").getDocument(source: .server) + _ = try await colRef.document("doc_b").getDocument(source: .server) + + try await db.disableNetwork() + + // Execute offline query which requires full local index scan + let query = colRef.order(by: FieldPath.documentID()).limit(to: 2) + let cacheSnapshot = try await query.getDocuments(source: .cache) + + XCTAssertEqual(cacheSnapshot.documents.count, 2) + if let firstDoc = cacheSnapshot.documents.first { + XCTAssertTrue(firstDoc.data().count > 0) + } + } + + func testWatchStreamInitializationAndDiff() async throws { + let docRef = collectionRef().document(DOC_15_9MB_UNICODE) + let expectation = XCTestExpectation(description: "Wait for 15.9MB Watch stream payload") + + // Attach listener, must not enter a CANCELLED retry loop + let listener = docRef.addSnapshotListener { snapshot, error in + XCTAssertNil(error) + guard let snapshot = snapshot else { return } + + if snapshot.exists { + expectation.fulfill() + } + } + + listener.remove() + + // TODO(dlarocque): Write streams for large docs are currently restricted in client SDKs. + // Once fixed, update this test to trigger a small field mutation and assert the + // listener fires a second time to test differential payload handling. + } + + // NOTE: This test will cause stream errors because writes are not working through watch. + func testOversizedPayloadRejection() async throws { + let docRef = collectionRef().document("temp_oversized_doc") + + // Generate ~16.1MB payload + let targetBytes = (16 * 1024 * 1024) + 102400 + let largePayload = generateString(sizeInBytes: targetBytes) + + do { + try await docRef.setData(["largeField": largePayload]) + XCTFail("Setting a document exceeding the 16MB limit should fail.") + } catch { + let nsError = error as NSError + XCTAssertEqual(nsError.domain, FirestoreErrorDomain) + // Must map to standard InvalidArgument/MongoDB compatibility error, not a crash. + XCTAssertEqual(nsError.code, FirestoreErrorCode.invalidArgument.rawValue) + } + } + + func testTransactionReadModifyWrite() async throws { + let docRef = collectionRef().document(DOC_15_9MB_UNICODE) + + do { + _ = try await db.runTransaction { (transaction, errorPointer) -> Any? in + do { + _ = try transaction.getDocument(docRef) + // Minor write to test transaction commit payload limits + transaction.updateData(["transaction_timestamp": FieldValue.serverTimestamp()], forDocument: docRef) + } catch let fetchError as NSError { + errorPointer?.pointee = fetchError + } + return nil + } + } catch { + XCTFail("Transaction failed with error: \(error)") + } + } +} diff --git a/Firestore/core/src/remote/grpc_connection.cc b/Firestore/core/src/remote/grpc_connection.cc index 78de37b5636..91ea8c8a972 100644 --- a/Firestore/core/src/remote/grpc_connection.cc +++ b/Firestore/core/src/remote/grpc_connection.cc @@ -306,6 +306,11 @@ std::shared_ptr GrpcConnection::CreateChannel() const { // This acts as a failsafe.) args.SetInt(GRPC_ARG_KEEPALIVE_TIME_MS, 30 * 1000); + // Increase the max receive message size to 17MB, to support 16MB documents + // + overhead. + const int GRPC_MAX_RECEIVE_MESSAGE_SIZE = 17 * 1024 * 1024; + args.SetMaxReceiveMessageSize(GRPC_MAX_RECEIVE_MESSAGE_SIZE); + const HostConfig* host_config = Config().find(host); if (!host_config) { std::string root_certificate = LoadGrpcRootCertificate(); From 6048dde477fd2ac0bc332903400f745cd9d25571 Mon Sep 17 00:00:00 2001 From: dlarocque Date: Mon, 1 Jun 2026 09:19:26 -0500 Subject: [PATCH 2/9] add opt in to large doc tests --- .../Swift/Tests/Integration/LargeDocIntegrationTests.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift b/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift index 98bb1219e27..6d325cec084 100644 --- a/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift +++ b/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift @@ -28,6 +28,13 @@ final class LargeDocIntegrationTests: FSTIntegrationTestCase { override func setUp() async throws { try await super.setUp() + // Skip by default to prevent slowing down CI. + // Add environment variable FIRESTORE_RUN_LARGE_DOC_TESTS = YES. + let runLargeTests = ProcessInfo.processInfo.environment["FIRESTORE_RUN_LARGE_DOC_TESTS"] + if runLargeTests != "YES" && runLargeTests != "true" { + throw XCTSkip("Skipping large doc tests. Set FIRESTORE_RUN_LARGE_DOC_TESTS=YES to run.") + } + // Skip tests if the backend edition is not supported or if not nightly if FSTIntegrationTestCase.targetBackend() != .nightly || FSTIntegrationTestCase .backendEdition() == .standard { From 5bc801251e6518892c9aea43e78f8d50e09d7540 Mon Sep 17 00:00:00 2001 From: dlarocque Date: Thu, 11 Jun 2026 10:12:08 -0500 Subject: [PATCH 3/9] formatting --- .../Integration/LargeDocIntegrationTests.swift | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift b/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift index 6d325cec084..169375d9847 100644 --- a/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift +++ b/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift @@ -84,7 +84,7 @@ final class LargeDocIntegrationTests: FSTIntegrationTestCase { // Execute offline query which requires full local index scan let query = colRef.order(by: FieldPath.documentID()).limit(to: 2) let cacheSnapshot = try await query.getDocuments(source: .cache) - + XCTAssertEqual(cacheSnapshot.documents.count, 2) if let firstDoc = cacheSnapshot.documents.first { XCTAssertTrue(firstDoc.data().count > 0) @@ -106,7 +106,7 @@ final class LargeDocIntegrationTests: FSTIntegrationTestCase { } listener.remove() - + // TODO(dlarocque): Write streams for large docs are currently restricted in client SDKs. // Once fixed, update this test to trigger a small field mutation and assert the // listener fires a second time to test differential payload handling. @@ -117,9 +117,9 @@ final class LargeDocIntegrationTests: FSTIntegrationTestCase { let docRef = collectionRef().document("temp_oversized_doc") // Generate ~16.1MB payload - let targetBytes = (16 * 1024 * 1024) + 102400 + let targetBytes = (16 * 1024 * 1024) + 102_400 let largePayload = generateString(sizeInBytes: targetBytes) - + do { try await docRef.setData(["largeField": largePayload]) XCTFail("Setting a document exceeding the 16MB limit should fail.") @@ -133,13 +133,16 @@ final class LargeDocIntegrationTests: FSTIntegrationTestCase { func testTransactionReadModifyWrite() async throws { let docRef = collectionRef().document(DOC_15_9MB_UNICODE) - + do { - _ = try await db.runTransaction { (transaction, errorPointer) -> Any? in + _ = try await db.runTransaction { transaction, errorPointer -> Any? in do { _ = try transaction.getDocument(docRef) // Minor write to test transaction commit payload limits - transaction.updateData(["transaction_timestamp": FieldValue.serverTimestamp()], forDocument: docRef) + transaction.updateData( + ["transaction_timestamp": FieldValue.serverTimestamp()], + forDocument: docRef + ) } catch let fetchError as NSError { errorPointer?.pointee = fetchError } From e62d2c6e7a8bcf402732ef9f949115ad7b1c5926 Mon Sep 17 00:00:00 2001 From: dlarocque Date: Thu, 11 Jun 2026 10:13:36 -0500 Subject: [PATCH 4/9] Add changelog entry --- Firestore/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index b6957cb42c9..d535efad793 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -1,3 +1,7 @@ +# Unreleased + +- [feature] Add support for 16MB documents. TODO(dlarocque): add more details + # 12.15.0 - [fix] Remove use of designated initializers for `forceIndex` [#16229]. From 951382f52a793dc9335b6f5003481c5d3173994f Mon Sep 17 00:00:00 2001 From: dlarocque Date: Mon, 6 Jul 2026 09:40:28 -0400 Subject: [PATCH 5/9] Write tests --- .../LargeDocIntegrationTests.swift | 98 +++++++++++++++---- 1 file changed, 78 insertions(+), 20 deletions(-) diff --git a/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift b/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift index 169375d9847..54590e91554 100644 --- a/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift +++ b/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift @@ -20,13 +20,12 @@ import XCTest @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) final class LargeDocIntegrationTests: FSTIntegrationTestCase { - // Test data prerequisites populated by the nightly seeder script. - let SEED_COLLECTION = "serverSdkTests" - let DOC_15_9MB_UNICODE = "doc_15_9MB_unicode" - let COL_LARGE_DOCS = "col_large_docs" + private static var seedCollection: String? + private static var sharedDb: Firestore? override func setUp() async throws { try await super.setUp() + LargeDocIntegrationTests.sharedDb = self.db // Skip by default to prevent slowing down CI. // Add environment variable FIRESTORE_RUN_LARGE_DOC_TESTS = YES. @@ -40,6 +39,38 @@ final class LargeDocIntegrationTests: FSTIntegrationTestCase { .backendEdition() == .standard { throw XCTSkip("Skipping large document tests because backend is not compatible.") } + + if LargeDocIntegrationTests.seedCollection == nil { + LargeDocIntegrationTests.seedCollection = "large_doc_tests_ios_\(UUID().uuidString)" + } + let col = LargeDocIntegrationTests.seedCollection! + + // Self-seeding check: ensure prerequisite documents exist for read tests. + let docRef = db.collection(col).document("doc_15_9MB_unicode") + let docA = db.collection(col).document("doc_a") + let docB = db.collection(col).document("doc_b") + + if try await docRef.getDocument(source: .server).exists != true { + let targetBytes = Int(15.9 * 1024 * 1024) + let payload = generateString(sizeInBytes: targetBytes) + try await docRef.setData(["chunk": payload]) + try await docA.setData(["chunk": payload]) + try await docB.setData(["chunk": payload]) + } + } + + override class func tearDown() { + if let db = sharedDb, let col = seedCollection { + let sem = DispatchSemaphore(value: 0) + Task { + try? await db.collection(col).document("doc_15_9MB_unicode").delete() + try? await db.collection(col).document("doc_a").delete() + try? await db.collection(col).document("doc_b").delete() + sem.signal() + } + _ = sem.wait(timeout: .now() + 30) + } + super.tearDown() } // MARK: - Helper Methods @@ -49,13 +80,13 @@ final class LargeDocIntegrationTests: FSTIntegrationTestCase { } override func collectionRef() -> CollectionReference { - return db.collection(SEED_COLLECTION) + return db.collection(LargeDocIntegrationTests.seedCollection!) } // MARK: - Test Cases func testReadAndCacheLargeUnicodeDocument() async throws { - let docRef = collectionRef().document(DOC_15_9MB_UNICODE) + let docRef = collectionRef().document("doc_15_9MB_unicode") defer { Task { try? await db.enableNetwork() } } let serverSnapshot = try await docRef.getDocument(source: .server) @@ -72,7 +103,7 @@ final class LargeDocIntegrationTests: FSTIntegrationTestCase { } func testQueryLargeDocumentsForcesLocalScan() async throws { - let colRef = db.collection(COL_LARGE_DOCS) + let colRef = collectionRef() defer { Task { try? await db.enableNetwork() } } // Populate cache @@ -92,31 +123,26 @@ final class LargeDocIntegrationTests: FSTIntegrationTestCase { } func testWatchStreamInitializationAndDiff() async throws { - let docRef = collectionRef().document(DOC_15_9MB_UNICODE) - let expectation = XCTestExpectation(description: "Wait for 15.9MB Watch stream payload") + let docRef = collectionRef().document("doc_15_9MB_unicode") + let expectation = XCTestExpectation(description: "Wait for differential update payload") // Attach listener, must not enter a CANCELLED retry loop let listener = docRef.addSnapshotListener { snapshot, error in XCTAssertNil(error) guard let snapshot = snapshot else { return } - if snapshot.exists { + if snapshot.exists && snapshot.data()?["differential_field"] != nil { expectation.fulfill() } } + defer { listener.remove() } - listener.remove() - - // TODO(dlarocque): Write streams for large docs are currently restricted in client SDKs. - // Once fixed, update this test to trigger a small field mutation and assert the - // listener fires a second time to test differential payload handling. + try await docRef.updateData(["differential_field": "updated_value"]) + await fulfillment(of: [expectation], timeout: 60) } - // NOTE: This test will cause stream errors because writes are not working through watch. func testOversizedPayloadRejection() async throws { let docRef = collectionRef().document("temp_oversized_doc") - - // Generate ~16.1MB payload let targetBytes = (16 * 1024 * 1024) + 102_400 let largePayload = generateString(sizeInBytes: targetBytes) @@ -131,14 +157,46 @@ final class LargeDocIntegrationTests: FSTIntegrationTestCase { } } + func testWriteValidLargeDocument() async throws { + let tempDocId = "temp_valid_large_doc_\(UUID().uuidString)" + let docRef = collectionRef().document(tempDocId) + defer { Task { try? await docRef.delete() } } + + let targetBytes = Int(15.9 * 1024 * 1024) + let largePayload = generateString(sizeInBytes: targetBytes) + + try await docRef.setData(["chunk": largePayload]) + + let snapshot = try await docRef.getDocument(source: .server) + XCTAssertTrue(snapshot.exists) + XCTAssertEqual(snapshot.data()?["chunk"] as? String, largePayload) + } + + func testQueryLargeDocuments() async throws { + let colRef = collectionRef() + let query = colRef.whereField(FieldPath.documentID(), in: ["doc_a", "doc_b"]) + + let serverSnapshot = try await query.getDocuments(source: .server) + XCTAssertEqual(serverSnapshot.documents.count, 2) + + try await db.disableNetwork() + defer { Task { try? await db.enableNetwork() } } + + let cacheSnapshot = try await query.getDocuments(source: .cache) + XCTAssertEqual(cacheSnapshot.documents.count, 2) + + let serverFirstData = serverSnapshot.documents.first?.data() as NSDictionary? + let cacheFirstData = cacheSnapshot.documents.first?.data() as NSDictionary? + XCTAssertEqual(serverFirstData, cacheFirstData) + } + func testTransactionReadModifyWrite() async throws { - let docRef = collectionRef().document(DOC_15_9MB_UNICODE) + let docRef = collectionRef().document("doc_15_9MB_unicode") do { _ = try await db.runTransaction { transaction, errorPointer -> Any? in do { _ = try transaction.getDocument(docRef) - // Minor write to test transaction commit payload limits transaction.updateData( ["transaction_timestamp": FieldValue.serverTimestamp()], forDocument: docRef From a432642ce21af9b64ce8871455f4bf4315fbc253 Mon Sep 17 00:00:00 2001 From: dlarocque Date: Thu, 9 Jul 2026 12:51:35 -0400 Subject: [PATCH 6/9] self-seed large document tests --- .../LargeDocIntegrationTests.swift | 112 +++++++----------- .../Swift/Tests/Integration/TempTest.swift | 0 Firestore/core/src/remote/grpc_connection.cc | 7 +- 3 files changed, 47 insertions(+), 72 deletions(-) create mode 100644 Firestore/Swift/Tests/Integration/TempTest.swift diff --git a/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift b/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift index 54590e91554..9341639b90b 100644 --- a/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift +++ b/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift @@ -19,13 +19,9 @@ import Foundation import XCTest @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -final class LargeDocIntegrationTests: FSTIntegrationTestCase { - private static var seedCollection: String? - private static var sharedDb: Firestore? - - override func setUp() async throws { - try await super.setUp() - LargeDocIntegrationTests.sharedDb = self.db +class LargeDocIntegrationTests: FSTIntegrationTestCase { + override func setUpWithError() throws { + try super.setUpWithError() // Skip by default to prevent slowing down CI. // Add environment variable FIRESTORE_RUN_LARGE_DOC_TESTS = YES. @@ -39,60 +35,37 @@ final class LargeDocIntegrationTests: FSTIntegrationTestCase { .backendEdition() == .standard { throw XCTSkip("Skipping large document tests because backend is not compatible.") } - - if LargeDocIntegrationTests.seedCollection == nil { - LargeDocIntegrationTests.seedCollection = "large_doc_tests_ios_\(UUID().uuidString)" - } - let col = LargeDocIntegrationTests.seedCollection! - - // Self-seeding check: ensure prerequisite documents exist for read tests. - let docRef = db.collection(col).document("doc_15_9MB_unicode") - let docA = db.collection(col).document("doc_a") - let docB = db.collection(col).document("doc_b") - - if try await docRef.getDocument(source: .server).exists != true { - let targetBytes = Int(15.9 * 1024 * 1024) - let payload = generateString(sizeInBytes: targetBytes) - try await docRef.setData(["chunk": payload]) - try await docA.setData(["chunk": payload]) - try await docB.setData(["chunk": payload]) - } } override class func tearDown() { - if let db = sharedDb, let col = seedCollection { - let sem = DispatchSemaphore(value: 0) - Task { - try? await db.collection(col).document("doc_15_9MB_unicode").delete() - try? await db.collection(col).document("doc_a").delete() - try? await db.collection(col).document("doc_b").delete() - sem.signal() - } - _ = sem.wait(timeout: .now() + 30) - } super.tearDown() } // MARK: - Helper Methods - private func generateString(sizeInBytes: Int) -> String { - return String(repeating: "a", count: sizeInBytes) - } - - override func collectionRef() -> CollectionReference { - return db.collection(LargeDocIntegrationTests.seedCollection!) + private func generateString(sizeInBytes: Int, character: Character = "a") -> String { + return String(repeating: character, count: sizeInBytes) } + + private static var largeDocument: [String: Sendable] = ["chunk": String(repeating: "a", count: Int(15.9 * 1024 * 1024))] // MARK: - Test Cases + // NOTE: This test is currently expected to fail, because the payload + // size exceeds the gRPC message size limit. The Unicode character is + // encoded as 4 bytes, which causes the 16 MB document to be encoded + // as 64 MB. func testReadAndCacheLargeUnicodeDocument() async throws { - let docRef = collectionRef().document("doc_15_9MB_unicode") - defer { Task { try? await db.enableNetwork() } } + let colRef = collectionRef() + let db = colRef.firestore + let docRef = colRef.document("doc_15_9MB_unicode") + try await docRef.setData(["chunk": generateString(sizeInBytes: Int(15.9 * 1024 * 1024), character: "🚀")]) let serverSnapshot = try await docRef.getDocument(source: .server) XCTAssertTrue(serverSnapshot.exists) try await db.disableNetwork() + defer { Task { try? await db.enableNetwork() } } let cacheSnapshot = try await docRef.getDocument(source: .cache) XCTAssertTrue(cacheSnapshot.exists) @@ -104,18 +77,22 @@ final class LargeDocIntegrationTests: FSTIntegrationTestCase { func testQueryLargeDocumentsForcesLocalScan() async throws { let colRef = collectionRef() - defer { Task { try? await db.enableNetwork() } } + let db = colRef.firestore + let docRefA = colRef.document("doc_a") + let docRefB = colRef.document("doc_b") + try await docRefA.setData(LargeDocIntegrationTests.largeDocument) + try await docRefB.setData(LargeDocIntegrationTests.largeDocument) - // Populate cache - _ = try await colRef.document("doc_a").getDocument(source: .server) - _ = try await colRef.document("doc_b").getDocument(source: .server) + try await docRefA.getDocument(source: .server) + try await docRefB.getDocument(source: .server) try await db.disableNetwork() + defer { Task { try? await db.enableNetwork() } } // Execute offline query which requires full local index scan let query = colRef.order(by: FieldPath.documentID()).limit(to: 2) let cacheSnapshot = try await query.getDocuments(source: .cache) - + XCTAssertEqual(cacheSnapshot.documents.count, 2) if let firstDoc = cacheSnapshot.documents.first { XCTAssertTrue(firstDoc.data().count > 0) @@ -123,10 +100,13 @@ final class LargeDocIntegrationTests: FSTIntegrationTestCase { } func testWatchStreamInitializationAndDiff() async throws { - let docRef = collectionRef().document("doc_15_9MB_unicode") + let colRef = collectionRef() + let db = colRef.firestore + let docRef = colRef.document("doc_a") + try await docRef.setData(LargeDocIntegrationTests.largeDocument) + let expectation = XCTestExpectation(description: "Wait for differential update payload") - // Attach listener, must not enter a CANCELLED retry loop let listener = docRef.addSnapshotListener { snapshot, error in XCTAssertNil(error) guard let snapshot = snapshot else { return } @@ -152,28 +132,19 @@ final class LargeDocIntegrationTests: FSTIntegrationTestCase { } catch { let nsError = error as NSError XCTAssertEqual(nsError.domain, FirestoreErrorDomain) - // Must map to standard InvalidArgument/MongoDB compatibility error, not a crash. + // Must map to standard InvalidArgument error, not a crash. XCTAssertEqual(nsError.code, FirestoreErrorCode.invalidArgument.rawValue) } } - func testWriteValidLargeDocument() async throws { - let tempDocId = "temp_valid_large_doc_\(UUID().uuidString)" - let docRef = collectionRef().document(tempDocId) - defer { Task { try? await docRef.delete() } } - - let targetBytes = Int(15.9 * 1024 * 1024) - let largePayload = generateString(sizeInBytes: targetBytes) - - try await docRef.setData(["chunk": largePayload]) - - let snapshot = try await docRef.getDocument(source: .server) - XCTAssertTrue(snapshot.exists) - XCTAssertEqual(snapshot.data()?["chunk"] as? String, largePayload) - } - func testQueryLargeDocuments() async throws { - let colRef = collectionRef() + let colRef = collectionRef(withDocuments: TestHelper.largeDocs) + let db = colRef.firestore + let docRefA = colRef.document("doc_a") + let docRefB = colRef.document("doc_b") + try await docRefA.setData(LargeDocIntegrationTests.largeDocument) + try await docRefB.setData(LargeDocIntegrationTests.largeDocument) + let query = colRef.whereField(FieldPath.documentID(), in: ["doc_a", "doc_b"]) let serverSnapshot = try await query.getDocuments(source: .server) @@ -191,8 +162,11 @@ final class LargeDocIntegrationTests: FSTIntegrationTestCase { } func testTransactionReadModifyWrite() async throws { - let docRef = collectionRef().document("doc_15_9MB_unicode") - + let colRef = collectionRef(withDocuments: TestHelper.largeDocs) + let db = colRef.firestore + let docRef = colRef.document("doc_a") + try await docRef.setData(LargeDocIntegrationTests.largeDocument) + do { _ = try await db.runTransaction { transaction, errorPointer -> Any? in do { diff --git a/Firestore/Swift/Tests/Integration/TempTest.swift b/Firestore/Swift/Tests/Integration/TempTest.swift new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Firestore/core/src/remote/grpc_connection.cc b/Firestore/core/src/remote/grpc_connection.cc index 91ea8c8a972..d37f3850d64 100644 --- a/Firestore/core/src/remote/grpc_connection.cc +++ b/Firestore/core/src/remote/grpc_connection.cc @@ -306,10 +306,11 @@ std::shared_ptr GrpcConnection::CreateChannel() const { // This acts as a failsafe.) args.SetInt(GRPC_ARG_KEEPALIVE_TIME_MS, 30 * 1000); - // Increase the max receive message size to 17MB, to support 16MB documents + // Increase the max message size to 17MB, to support 16MB documents // + overhead. - const int GRPC_MAX_RECEIVE_MESSAGE_SIZE = 17 * 1024 * 1024; - args.SetMaxReceiveMessageSize(GRPC_MAX_RECEIVE_MESSAGE_SIZE); + const int GRPC_MAX_MESSAGE_SIZE = 17 * 1024 * 1024; + args.SetMaxReceiveMessageSize(GRPC_MAX_MESSAGE_SIZE); + args.SetMaxSendMessageSize(GRPC_MAX_MESSAGE_SIZE); const HostConfig* host_config = Config().find(host); if (!host_config) { From 24af9242945878d004e201f9bd37cffab22b71df Mon Sep 17 00:00:00 2001 From: dlarocque Date: Thu, 9 Jul 2026 14:09:57 -0400 Subject: [PATCH 7/9] clean up --- .../Swift/Tests/Integration/LargeDocIntegrationTests.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift b/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift index 9341639b90b..f25e64402d2 100644 --- a/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift +++ b/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift @@ -56,6 +56,8 @@ class LargeDocIntegrationTests: FSTIntegrationTestCase { // encoded as 4 bytes, which causes the 16 MB document to be encoded // as 64 MB. func testReadAndCacheLargeUnicodeDocument() async throws { + throw XCTSkip("Skipping unicode document test.") + let colRef = collectionRef() let db = colRef.firestore let docRef = colRef.document("doc_15_9MB_unicode") @@ -138,7 +140,7 @@ class LargeDocIntegrationTests: FSTIntegrationTestCase { } func testQueryLargeDocuments() async throws { - let colRef = collectionRef(withDocuments: TestHelper.largeDocs) + let colRef = collectionRef() let db = colRef.firestore let docRefA = colRef.document("doc_a") let docRefB = colRef.document("doc_b") @@ -162,7 +164,7 @@ class LargeDocIntegrationTests: FSTIntegrationTestCase { } func testTransactionReadModifyWrite() async throws { - let colRef = collectionRef(withDocuments: TestHelper.largeDocs) + let colRef = collectionRef() let db = colRef.firestore let docRef = colRef.document("doc_a") try await docRef.setData(LargeDocIntegrationTests.largeDocument) From e7b0d73e99bc98645fd591db68eecaed18bd4d9f Mon Sep 17 00:00:00 2001 From: dlarocque Date: Fri, 10 Jul 2026 09:44:28 -0400 Subject: [PATCH 8/9] format --- .../Integration/LargeDocIntegrationTests.swift | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift b/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift index f25e64402d2..c1a7164e4ae 100644 --- a/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift +++ b/Firestore/Swift/Tests/Integration/LargeDocIntegrationTests.swift @@ -46,8 +46,9 @@ class LargeDocIntegrationTests: FSTIntegrationTestCase { private func generateString(sizeInBytes: Int, character: Character = "a") -> String { return String(repeating: character, count: sizeInBytes) } - - private static var largeDocument: [String: Sendable] = ["chunk": String(repeating: "a", count: Int(15.9 * 1024 * 1024))] + + private static var largeDocument: [String: Sendable] = + ["chunk": String(repeating: "a", count: Int(15.9 * 1024 * 1024))] // MARK: - Test Cases @@ -61,7 +62,8 @@ class LargeDocIntegrationTests: FSTIntegrationTestCase { let colRef = collectionRef() let db = colRef.firestore let docRef = colRef.document("doc_15_9MB_unicode") - try await docRef.setData(["chunk": generateString(sizeInBytes: Int(15.9 * 1024 * 1024), character: "🚀")]) + try await docRef + .setData(["chunk": generateString(sizeInBytes: Int(15.9 * 1024 * 1024), character: "🚀")]) let serverSnapshot = try await docRef.getDocument(source: .server) XCTAssertTrue(serverSnapshot.exists) @@ -94,7 +96,7 @@ class LargeDocIntegrationTests: FSTIntegrationTestCase { // Execute offline query which requires full local index scan let query = colRef.order(by: FieldPath.documentID()).limit(to: 2) let cacheSnapshot = try await query.getDocuments(source: .cache) - + XCTAssertEqual(cacheSnapshot.documents.count, 2) if let firstDoc = cacheSnapshot.documents.first { XCTAssertTrue(firstDoc.data().count > 0) @@ -146,7 +148,7 @@ class LargeDocIntegrationTests: FSTIntegrationTestCase { let docRefB = colRef.document("doc_b") try await docRefA.setData(LargeDocIntegrationTests.largeDocument) try await docRefB.setData(LargeDocIntegrationTests.largeDocument) - + let query = colRef.whereField(FieldPath.documentID(), in: ["doc_a", "doc_b"]) let serverSnapshot = try await query.getDocuments(source: .server) @@ -168,7 +170,7 @@ class LargeDocIntegrationTests: FSTIntegrationTestCase { let db = colRef.firestore let docRef = colRef.document("doc_a") try await docRef.setData(LargeDocIntegrationTests.largeDocument) - + do { _ = try await db.runTransaction { transaction, errorPointer -> Any? in do { From 7bccea3abbdd3242003c8c5386af458cbef532b5 Mon Sep 17 00:00:00 2001 From: Daniel La Rocque Date: Fri, 10 Jul 2026 09:54:50 -0400 Subject: [PATCH 9/9] Delete Firestore/Swift/Tests/Integration/TempTest.swift --- Firestore/Swift/Tests/Integration/TempTest.swift | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Firestore/Swift/Tests/Integration/TempTest.swift diff --git a/Firestore/Swift/Tests/Integration/TempTest.swift b/Firestore/Swift/Tests/Integration/TempTest.swift deleted file mode 100644 index e69de29bb2d..00000000000