diff --git a/Tests/ApolloTests/DeferTests.swift b/Tests/ApolloTests/DeferTests.swift index de0ceef35..0a116a8a7 100644 --- a/Tests/ApolloTests/DeferTests.swift +++ b/Tests/ApolloTests/DeferTests.swift @@ -82,10 +82,11 @@ final class DeferTests: XCTestCase { // MARK: Parsing tests private func buildNetworkTransport( - responseData: Data + responseData: Data, + multipartBoundary boundary: String = "graphql" ) -> RequestChainNetworkTransport { let client = MockURLSessionClient( - response: .mock(headerFields: ["Content-Type": "multipart/mixed;boundary=graphql;deferSpec=20220824"]), + response: .mock(headerFields: ["Content-Type": "multipart/mixed;boundary=\(boundary);deferSpec=20220824"]), data: responseData ) @@ -225,7 +226,7 @@ final class DeferTests: XCTestCase { content-type: application/json { - "hasNext": true, + "hasNext": false, "incremental": [ { "label": "deferredGenres", @@ -445,4 +446,95 @@ final class DeferTests: XCTestCase { wait(for: [expectation], timeout: defaultTimeout) } + func test__parsing__givenPartialAndIncrementalResponses_withDashBoundaryInMessageBody_shouldNotSplitChunk() throws { + let multipartBoundary = "-" + let mysteryCharacterName = "lots\(multipartBoundary)of-\(multipartBoundary)similar--\(multipartBoundary)boundaries---\(multipartBoundary)in----\(multipartBoundary)this-----\(multipartBoundary)string" + + let network = buildNetworkTransport( + responseData: """ + + --\(multipartBoundary) + content-type: application/json + + { + "hasNext": true, + "data": { + "show" : { + "__typename": "show", + "name": "The Scooby-Doo Show", + "characters": [ + { + "__typename": "Character", + "name": "Scooby-Doo" + }, + { + "__typename": "Character", + "name": "Shaggy Rogers" + }, + { + "__typename": "Character", + "name": "Velma Dinkley" + }, + { + "__typename": "Character", + "name": "\(mysteryCharacterName)" + } + ] + } + } + } + --\(multipartBoundary) + content-type: application/json + + { + "hasNext": false, + "incremental": [ + { + "label": "deferredGenres", + "path": [ + "show" + ], + "data": { + "genres": [ + "Comedy", + "Mystery", + "Adventure" + ] + } + } + ] + } + --\(multipartBoundary)-- + """.crlfFormattedData(), + multipartBoundary: multipartBoundary + ) + + let expectation = expectation(description: "Result received") + expectation.expectedFulfillmentCount = 2 + + _ = network.send(operation: TVShowQuery()) { result in + defer { + expectation.fulfill() + } + + expect(result).to(beSuccess()) + + let data = try? result.get().data + expect(data?.__data._fulfilledFragments).to(equal([ + ObjectIdentifier(TVShowQuery.Data.self), + ])) + expect(data?.__data._deferredFragments).to(beEmpty()) + + let show = data?.show + if expectation.numberOfFulfillments == 0 { // Partial data + expect(show?.characters).to(haveCount(4)) + + let mysteryCharacter = show?.characters[3] + expect(mysteryCharacter?.name).to(equal(mysteryCharacterName)) + } + } + + wait(for: [expectation], timeout: defaultTimeout) + } + } diff --git a/Tests/ApolloTests/Interceptors/MultipartResponseSubscriptionParserTests.swift b/Tests/ApolloTests/Interceptors/MultipartResponseSubscriptionParserTests.swift index 0a3e53e07..d5ee9f209 100644 --- a/Tests/ApolloTests/Interceptors/MultipartResponseSubscriptionParserTests.swift +++ b/Tests/ApolloTests/Interceptors/MultipartResponseSubscriptionParserTests.swift @@ -261,10 +261,11 @@ final class MultipartResponseSubscriptionParserTests: XCTestCase { } private func buildNetworkTransport( - responseData: Data + responseData: Data, + multipartBoundary boundary: String = "graphql" ) -> RequestChainNetworkTransport { let client = MockURLSessionClient( - response: .mock(headerFields: ["Content-Type": "multipart/mixed;boundary=graphql;subscriptionSpec=1.0"]), + response: .mock(headerFields: ["Content-Type": "multipart/mixed;boundary=\(boundary);subscriptionSpec=1.0"]), data: responseData ) @@ -474,6 +475,51 @@ final class MultipartResponseSubscriptionParserTests: XCTestCase { wait(for: [expectation], timeout: defaultTimeout) } + func test__parsing__givenSingleChunk_withDashBoundaryInMessageBody_shouldReturnSuccess() throws { + let multipartBoundary = "-" + let network = buildNetworkTransport( + responseData: """ + + --\(multipartBoundary) + content-type: application/json + + { + "payload": { + "data": { + "__typename": "Time", + "ticker": 1, + "description": "lots\(multipartBoundary)of-\(multipartBoundary)similar--\(multipartBoundary)boundaries---\(multipartBoundary)in----\(multipartBoundary)this-----\(multipartBoundary)string" + } + } + } + --\(multipartBoundary)-- + """.crlfFormattedData(), + multipartBoundary: multipartBoundary + ) + + let expectedData = try Time(data: [ + "__typename": "Time", + "ticker": 1 + ], variables: nil) + + let expectation = expectation(description: "Multipart data received") + + _ = network.send(operation: MockSubscription