From 9ff89e07db7d9498980a1120412a915fa2750da2 Mon Sep 17 00:00:00 2001 From: Thomas Van Lenten Date: Fri, 26 Jun 2020 13:28:06 -0400 Subject: [PATCH] Handle parsing batch replies which minimal responses. A DELETE in a batch has no body, but it can also have very minimal headers, ensure a reply where the batch only has a status line and one (or no) header works correctly. Fixes #378 --- Source/GTLRCore.xcodeproj/project.pbxproj | 26 +++++------ Source/Objects/GTLRService.m | 44 ++++++++++++------- .../Tests/Data/NoPayloadsBatch.response.txt | 16 +++++++ Source/Tests/GTLRServiceTest.m | 37 +++++++++++++++- 4 files changed, 91 insertions(+), 32 deletions(-) create mode 100644 Source/Tests/Data/NoPayloadsBatch.response.txt diff --git a/Source/GTLRCore.xcodeproj/project.pbxproj b/Source/GTLRCore.xcodeproj/project.pbxproj index 21d555f1d..7c482c7a4 100644 --- a/Source/GTLRCore.xcodeproj/project.pbxproj +++ b/Source/GTLRCore.xcodeproj/project.pbxproj @@ -33,8 +33,6 @@ 4F3E9C931C6C32B7004192DE /* Drive1BatchPaging2.response.txt in Resources */ = {isa = PBXBuildFile; fileRef = 4F3E9C8E1C6C32B7004192DE /* Drive1BatchPaging2.response.txt */; }; 4F3E9C941C6C32B7004192DE /* Drive1BatchPaging3.response.txt in Resources */ = {isa = PBXBuildFile; fileRef = 4F3E9C8F1C6C32B7004192DE /* Drive1BatchPaging3.response.txt */; }; 4F3E9C951C6C32B7004192DE /* Drive1BatchPaging3.response.txt in Resources */ = {isa = PBXBuildFile; fileRef = 4F3E9C8F1C6C32B7004192DE /* Drive1BatchPaging3.response.txt */; }; - 4F6F2E721C615492001065A5 /* Drive1BatchCorrupt.response.txt in Resources */ = {isa = PBXBuildFile; fileRef = 4F6F2E701C615492001065A5 /* Drive1BatchCorrupt.response.txt */; }; - 4F6F2E731C615492001065A5 /* Drive1BatchCorrupt.response.txt in Resources */ = {isa = PBXBuildFile; fileRef = 4F6F2E701C615492001065A5 /* Drive1BatchCorrupt.response.txt */; }; 4F6F2E7A1C6154AA001065A5 /* Drive1Corrupt.response.txt in Resources */ = {isa = PBXBuildFile; fileRef = 4F6F2E771C6154AA001065A5 /* Drive1Corrupt.response.txt */; }; 4F6F2E7B1C6154AA001065A5 /* Drive1Corrupt.response.txt in Resources */ = {isa = PBXBuildFile; fileRef = 4F6F2E771C6154AA001065A5 /* Drive1Corrupt.response.txt */; }; 4F934E671512712100C4EA34 /* GTLRBase64.h in Headers */ = {isa = PBXBuildFile; fileRef = 4F934E651512712100C4EA34 /* GTLRBase64.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -389,6 +387,11 @@ F4D9D52A1D833D7C000EBBED /* GTLRDuration.m in Sources */ = {isa = PBXBuildFile; fileRef = F4D9D51E1D832DB5000EBBED /* GTLRDuration.m */; }; F4D9D52B1D833D7C000EBBED /* GTLRDuration.m in Sources */ = {isa = PBXBuildFile; fileRef = F4D9D51E1D832DB5000EBBED /* GTLRDuration.m */; }; F4D9D52C1D833D7D000EBBED /* GTLRDuration.m in Sources */ = {isa = PBXBuildFile; fileRef = F4D9D51E1D832DB5000EBBED /* GTLRDuration.m */; }; + F4F2CFDA24A6530900F1B3A7 /* NoPayloadsBatch.response.txt in Resources */ = {isa = PBXBuildFile; fileRef = F4F2CFD924A6530900F1B3A7 /* NoPayloadsBatch.response.txt */; }; + F4F2CFDB24A6530900F1B3A7 /* NoPayloadsBatch.response.txt in Resources */ = {isa = PBXBuildFile; fileRef = F4F2CFD924A6530900F1B3A7 /* NoPayloadsBatch.response.txt */; }; + F4F2CFDC24A6530900F1B3A7 /* NoPayloadsBatch.response.txt in Resources */ = {isa = PBXBuildFile; fileRef = F4F2CFD924A6530900F1B3A7 /* NoPayloadsBatch.response.txt */; }; + F4F2CFDD24A6674B00F1B3A7 /* Drive1BatchCorrupt.response.txt in Resources */ = {isa = PBXBuildFile; fileRef = F4ACE7E91DAD217700053F91 /* Drive1BatchCorrupt.response.txt */; }; + F4F2CFDE24A6674C00F1B3A7 /* Drive1BatchCorrupt.response.txt in Resources */ = {isa = PBXBuildFile; fileRef = F4ACE7E91DAD217700053F91 /* Drive1BatchCorrupt.response.txt */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -421,7 +424,6 @@ 4F3E9C8D1C6C32B7004192DE /* Drive1BatchPaging1.response.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Drive1BatchPaging1.response.txt; path = Tests/Data/Drive1BatchPaging1.response.txt; sourceTree = ""; }; 4F3E9C8E1C6C32B7004192DE /* Drive1BatchPaging2.response.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Drive1BatchPaging2.response.txt; path = Tests/Data/Drive1BatchPaging2.response.txt; sourceTree = ""; }; 4F3E9C8F1C6C32B7004192DE /* Drive1BatchPaging3.response.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Drive1BatchPaging3.response.txt; path = Tests/Data/Drive1BatchPaging3.response.txt; sourceTree = ""; }; - 4F6F2E701C615492001065A5 /* Drive1BatchCorrupt.response.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Drive1BatchCorrupt.response.txt; path = Tests/Data/Drive1BatchCorrupt.response.txt; sourceTree = ""; }; 4F6F2E771C6154AA001065A5 /* Drive1Corrupt.response.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Drive1Corrupt.response.txt; path = Tests/Data/Drive1Corrupt.response.txt; sourceTree = ""; }; 4F934E651512712100C4EA34 /* GTLRBase64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTLRBase64.h; path = Utilities/GTLRBase64.h; sourceTree = ""; }; 4F934E661512712100C4EA34 /* GTLRBase64.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTLRBase64.m; path = Utilities/GTLRBase64.m; sourceTree = ""; }; @@ -514,6 +516,7 @@ F4D9D51D1D832DB5000EBBED /* GTLRDuration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTLRDuration.h; path = Objects/GTLRDuration.h; sourceTree = ""; }; F4D9D51E1D832DB5000EBBED /* GTLRDuration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTLRDuration.m; path = Objects/GTLRDuration.m; sourceTree = ""; }; F4D9D5251D833D5D000EBBED /* GTLRDurationTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTLRDurationTest.m; path = Tests/GTLRDurationTest.m; sourceTree = ""; }; + F4F2CFD924A6530900F1B3A7 /* NoPayloadsBatch.response.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = NoPayloadsBatch.response.txt; path = Tests/Data/NoPayloadsBatch.response.txt; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -615,7 +618,6 @@ 089C1671FE841209C02AAC07 /* Frameworks and Libraries */, 19C28FB8FE9D52D311CA2CBB /* Products */, F4A872741DAD1ED800D69E09 /* Frameworks */, - 34AF73421FB4E3C80022335F /* Recovered References */, ); name = GoogleBundle; sourceTree = ""; @@ -678,14 +680,6 @@ name = "GTLR Source"; sourceTree = ""; }; - 34AF73421FB4E3C80022335F /* Recovered References */ = { - isa = PBXGroup; - children = ( - 4F6F2E701C615492001065A5 /* Drive1BatchCorrupt.response.txt */, - ); - name = "Recovered References"; - sourceTree = ""; - }; 4F346DD20D7500C9006033E0 /* Common */ = { isa = PBXGroup; children = ( @@ -818,6 +812,7 @@ 4FA824881C6BDAC700E11CCC /* Drive1Paging3.response.txt */, 4F3AA9161C600013008497BC /* Drive1ParamError.response.txt */, F4A07B1B1E4293990035678A /* Drive1ParamErrorAsList.response.txt */, + F4F2CFD924A6530900F1B3A7 /* NoPayloadsBatch.response.txt */, F4469DFE1C45757E00BCFAA1 /* URITemplateExtraTests.json */, F4469DFF1C45757E00BCFAA1 /* URITemplateRFCTests.json */, ); @@ -1188,6 +1183,7 @@ F4469E051C45758700BCFAA1 /* URITemplateRFCTests.json in Resources */, F4469E041C45758700BCFAA1 /* URITemplateExtraTests.json in Resources */, 4F3AA91A1C600013008497BC /* Drive1ParamError.response.txt in Resources */, + F4F2CFDA24A6530900F1B3A7 /* NoPayloadsBatch.response.txt in Resources */, 4F3E9C931C6C32B7004192DE /* Drive1BatchPaging2.response.txt in Resources */, 4F3E9C811C6C12A1004192DE /* Classroom1NotFoundError.response.txt in Resources */, 4F3E9C911C6C32B7004192DE /* Drive1BatchPaging1.response.txt in Resources */, @@ -1196,10 +1192,10 @@ 4FA8248E1C6BDAC700E11CCC /* Drive1Paging3.response.txt in Resources */, F4A07B1F1E4293A10035678A /* Drive1ParamErrorAsList.response.txt in Resources */, 4F3AA9181C600013008497BC /* Drive1AuthError.response.txt in Resources */, - 4F6F2E731C615492001065A5 /* Drive1BatchCorrupt.response.txt in Resources */, 4FA8248A1C6BDAC700E11CCC /* Drive1Paging1.response.txt in Resources */, 4F6F2E7B1C6154AA001065A5 /* Drive1Corrupt.response.txt in Resources */, 4F2591411C5C3338009E0240 /* Drive1.response.txt in Resources */, + F4F2CFDE24A6674C00F1B3A7 /* Drive1BatchCorrupt.response.txt in Resources */, 4FA8248C1C6BDAC700E11CCC /* Drive1Paging2.response.txt in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1219,6 +1215,7 @@ F4469E031C45758600BCFAA1 /* URITemplateRFCTests.json in Resources */, 4F3AA9191C600013008497BC /* Drive1ParamError.response.txt in Resources */, F4469E021C45758600BCFAA1 /* URITemplateExtraTests.json in Resources */, + F4F2CFDB24A6530900F1B3A7 /* NoPayloadsBatch.response.txt in Resources */, 4F3E9C921C6C32B7004192DE /* Drive1BatchPaging2.response.txt in Resources */, 4F3E9C801C6C12A1004192DE /* Classroom1NotFoundError.response.txt in Resources */, 4F3E9C901C6C32B7004192DE /* Drive1BatchPaging1.response.txt in Resources */, @@ -1227,10 +1224,10 @@ 4FA8248D1C6BDAC700E11CCC /* Drive1Paging3.response.txt in Resources */, F4A07B201E4293A20035678A /* Drive1ParamErrorAsList.response.txt in Resources */, 4F3AA9171C600013008497BC /* Drive1AuthError.response.txt in Resources */, - 4F6F2E721C615492001065A5 /* Drive1BatchCorrupt.response.txt in Resources */, 4FA824891C6BDAC700E11CCC /* Drive1Paging1.response.txt in Resources */, 4F6F2E7A1C6154AA001065A5 /* Drive1Corrupt.response.txt in Resources */, 4F2591401C5C3338009E0240 /* Drive1.response.txt in Resources */, + F4F2CFDD24A6674B00F1B3A7 /* Drive1BatchCorrupt.response.txt in Resources */, 4FA8248B1C6BDAC700E11CCC /* Drive1Paging2.response.txt in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1256,6 +1253,7 @@ F4ACE7DA1DAD20E100053F91 /* Classroom1NotFoundError.response.txt in Resources */, F4ACE7DF1DAD20E100053F91 /* Drive1BatchPaging3.response.txt in Resources */, F4ACE7E61DAD20E100053F91 /* Drive1Paging3.response.txt in Resources */, + F4F2CFDC24A6530900F1B3A7 /* NoPayloadsBatch.response.txt in Resources */, F4ACE7E41DAD20E100053F91 /* Drive1Paging1.response.txt in Resources */, F4ACE7E31DAD20E100053F91 /* Drive1Corrupt.response.txt in Resources */, F4ACE7E51DAD20E100053F91 /* Drive1Paging2.response.txt in Resources */, diff --git a/Source/Objects/GTLRService.m b/Source/Objects/GTLRService.m index 2c80275af..a556bb7ad 100644 --- a/Source/Objects/GTLRService.m +++ b/Source/Objects/GTLRService.m @@ -1566,33 +1566,38 @@ - (GTLRBatchResponsePart *)responsePartWithMIMEPart:(GTMMIMEDocumentPart *)mimeP targetBytes:"\r\n" targetLength:2 foundOffsets:&offsets]; - if (offsets.count < 2) { - // Lack of status line and inner headers is strange, but not fatal since - // if the JSON was delivered. - GTLR_DEBUG_LOG(@"GTLRService: Batch result cannot parse headers for request %@:\n%@", - responseContentID, - [[NSString alloc] initWithData:innerHeaderData - encoding:NSUTF8StringEncoding]); - } else { - NSString *statusString; - NSInteger statusCode; - [self getResponseLineFromData:innerHeaderData - statusCode:&statusCode - statusString:&statusString]; - responsePart.statusCode = statusCode; - responsePart.statusString = statusString; + NSData *statusLine; + NSData *actualInnerHeaderData; + if (offsets.count) { + NSRange statusRange = NSMakeRange(0, offsets[0].unsignedIntegerValue); + statusLine = [innerHeaderData subdataWithRange:statusRange]; NSUInteger actualInnerHeaderOffset = offsets[0].unsignedIntegerValue + 2; - NSData *actualInnerHeaderData; if (innerHeaderData.length - actualInnerHeaderOffset > 0) { NSRange actualInnerHeaderRange = NSMakeRange(actualInnerHeaderOffset, innerHeaderData.length - actualInnerHeaderOffset); actualInnerHeaderData = [innerHeaderData subdataWithRange:actualInnerHeaderRange]; } - responsePart.headers = [GTMMIMEDocument headersWithData:actualInnerHeaderData]; + } else { + // There appears to only be a status line. + // + // This means there were no reponse headers. "Date" seems like it should + // be required, but https://tools.ietf.org/html/rfc7231#section-7.1.1.2 + // lets even that be left off if a server doesn't have a clock it knows + // to be correct. + statusLine = innerHeaderData; } + NSString *statusString; + NSInteger statusCode; + [self getResponseLineFromData:statusLine + statusCode:&statusCode + statusString:&statusString]; + responsePart.statusCode = statusCode; + responsePart.statusString = statusString; + responsePart.headers = [GTMMIMEDocument headersWithData:actualInnerHeaderData]; + // Create JSON from the body. // (if there is any, methods like delete return nothing) NSMutableDictionary *json; @@ -1644,6 +1649,11 @@ - (void)getResponseLineFromData:(NSData *)data && [scanner scanInteger:outStatusCode] && [scanner scanUpToCharactersFromSet:newlineSet intoString:outStatusString]) { // Got it all. + #if DEBUG + if (![httpVersion hasPrefix:@"HTTP/"]) { + GTLR_DEBUG_LOG(@"GTLRService: Non-standard HTTP Version: %@", httpVersion); + } + #endif } } diff --git a/Source/Tests/Data/NoPayloadsBatch.response.txt b/Source/Tests/Data/NoPayloadsBatch.response.txt new file mode 100644 index 000000000..5295c6090 --- /dev/null +++ b/Source/Tests/Data/NoPayloadsBatch.response.txt @@ -0,0 +1,16 @@ +--batch_hXL_3_UnLsQ_AAYW6kQ4LYU +Content-Type: application/http +Content-ID: response-gtlr_39 + +HTTP/1.1 204 No Content +Date: Wed, 24 Jun 2020 09:21:24 GMT + + +--batch_hXL_3_UnLsQ_AAYW6kQ4LYU +Content-Type: application/http +Content-ID: response-gtlr_40 + +HTTP/1.1 204 No Content + + +--batch_hXL_3_UnLsQ_AAYW6kQ4LYU diff --git a/Source/Tests/GTLRServiceTest.m b/Source/Tests/GTLRServiceTest.m index e8c19a7e5..cca7906b5 100644 --- a/Source/Tests/GTLRServiceTest.m +++ b/Source/Tests/GTLRServiceTest.m @@ -56,7 +56,10 @@ @implementation GTLRTestingSvc_FileList_Surrogate2 // Internal methods redeclared for testing. @interface GTLRBatchResponsePart : NSObject +@property(nonatomic, assign) NSInteger statusCode; +@property(nonatomic, copy) NSString *statusString; @property(nonatomic, strong) NSDictionary *headers; +@property(nonatomic, strong) NSError *parseError; @end @interface GTLRService (InternalMethods) @@ -147,7 +150,8 @@ + (NSData *)dataForTestFileName:(NSString *)fileName { " \"Drive1Empty.response.txt\": \"ewp9Cg==\","\ " \"Drive1Batch.response.txt\": \"LS1iYXRjaF8zYWpONDBZcFhaUV9BQmY1d3dfZ3h5Zw0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi9odHRwDQpDb250ZW50LUlEOiByZXNwb25zZS1ndGxyXzQNCg0KSFRUUC8xLjEgNDA0IE5vdCBGb3VuZA0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi9qc29uOyBjaGFyc2V0PVVURi04DQpEYXRlOiBNb24sIDAxIEZlYiAyMDE2IDIyOjUwOjU3IEdNVA0KRXhwaXJlczogTW9uLCAwMSBGZWIgMjAxNiAyMjo1MDo1NyBHTVQNCkNhY2hlLUNvbnRyb2w6IHByaXZhdGUsIG1heC1hZ2U9MA0KQ29udGVudC1MZW5ndGg6IDE3MDgyDQpYLVJlamVjdGVkLVJlYXNvbjogRmFpbGVkIHRvIHJlbW92ZSBFeGNhbGlidXIgZnJvbSBzdG9uZQ0KDQp7DQogImVycm9yIjogew0KICAiZXJyb3JzIjogWw0KICAgew0KICAgICJkb21haW4iOiAiZ2xvYmFsIiwNCiAgICAicmVhc29uIjogIm5vdEZvdW5kIiwNCiAgICAibWVzc2FnZSI6ICJGaWxlIG5vdCBmb3VuZDogYmFkSUQuIiwNCiAgICAibG9jYXRpb25UeXBlIjogInBhcmFtZXRlciIsDQogICAgImxvY2F0aW9uIjogImZpbGVJZCIsDQogICAgImRlYnVnSW5mbyI6ICJjb2RlOiBOT1RfRk9VTkQiDQogICB9DQogIF0sDQogICJjb2RlIjogNDA0LA0KICAibWVzc2FnZSI6ICJGaWxlIG5vdCBmb3VuZDogYmFkSUQuIg0KIH0NCn0NCg0KLS1iYXRjaF8zYWpONDBZcFhaUV9BQmY1d3dfZ3h5Zw0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi9odHRwDQpDb250ZW50LUlEOiByZXNwb25zZS1ndGxyXzUNCg0KSFRUUC8xLjEgMjAwIE9LDQpDb250ZW50LVR5cGU6IGFwcGxpY2F0aW9uL2pzb247IGNoYXJzZXQ9VVRGLTgNCkRhdGU6IE1vbiwgMDEgRmViIDIwMTYgMjI6NTA6NTcgR01UDQpFeHBpcmVzOiBNb24sIDAxIEZlYiAyMDE2IDIyOjUwOjU3IEdNVA0KQ2FjaGUtQ29udHJvbDogcHJpdmF0ZSwgbWF4LWFnZT0wDQpDb250ZW50LUxlbmd0aDogNDQNClJldHJ5LUFmdGVyOiAzMDANCg0Kew0KICJraW5kIjogImRyaXZlI2ZpbGVMaXN0IiwNCiAiZmlsZXMiOiBbXQ0KfQ0KDQotLWJhdGNoXzNhak40MFlwWFpRX0FCZjV3d19neHlnDQpDb250ZW50LVR5cGU6IGFwcGxpY2F0aW9uL2h0dHANCkNvbnRlbnQtSUQ6IHJlc3BvbnNlLWd0bHJfNg0KDQpIVFRQLzEuMSAyMDAgT0sNCkNvbnRlbnQtVHlwZTogYXBwbGljYXRpb24vanNvbjsgY2hhcnNldD1VVEYtOA0KRGF0ZTogTW9uLCAwMSBGZWIgMjAxNiAyMjo1MDo1NyBHTVQNCkV4cGlyZXM6IE1vbiwgMDEgRmViIDIwMTYgMjI6NTA6NTcgR01UDQpDYWNoZS1Db250cm9sOiBwcml2YXRlLCBtYXgtYWdlPTANCkNvbnRlbnQtTGVuZ3RoOiA0NQ0KDQp7DQogInBhcmVudHMiOiBbDQogICIwQUxzdlpERHd0S3JoVWs5UFZBIg0KIF0NCn0NCg0KLS1iYXRjaF8zYWpONDBZcFhaUV9BQmY1d3dfZ3h5Zy0tDQo=\","\ " \"Drive1Corrupt.response.txt\": \"eyBhYmMgOjo9PSB9Cg==\","\ - " \"Drive1Paging3.response.txt\": \"ewogICJraW5kIiA6ICJkcml2ZSNmaWxlTGlzdCIsCiAgImZpbGVzIiA6IFsKICAgIHsKICAgICAgIm1pbWVUeXBlIiA6ICJhcHBsaWNhdGlvblwvdm5kLmdvb2dsZS1hcHBzLnNwcmVhZHNoZWV0IiwKICAgICAgImlkIiA6ICIxZEtjQWJic3Y2UHJMYWdvTDJEX3I2IiwKICAgICAgImtpbmQiIDogImRyaXZlI2ZpbGUiLAogICAgICAibmFtZSIgOiAiMjAwNiB0YXggZGVkdWN0aW9ucyIsCiAgICAgICJ0cmFzaGVkIiA6IGZhbHNlCiAgICB9LAogICAgewogICAgICAibWltZVR5cGUiIDogImFwcGxpY2F0aW9uXC92bmQuZ29vZ2xlLWFwcHMuZG9jdW1lbnQiLAogICAgICAiaWQiIDogIjFNMFhZakdoRWJZMUJ6SHM1c3JPcFEiLAogICAgICAia2luZCIgOiAiZHJpdmUjZmlsZSIsCiAgICAgICJuYW1lIiA6ICJBbm90aGVyIGRvYyIsCiAgICAgICJ0cmFzaGVkIiA6IGZhbHNlCiAgICB9CiAgXQp9Cg==\""\ + " \"Drive1Paging3.response.txt\": \"ewogICJraW5kIiA6ICJkcml2ZSNmaWxlTGlzdCIsCiAgImZpbGVzIiA6IFsKICAgIHsKICAgICAgIm1pbWVUeXBlIiA6ICJhcHBsaWNhdGlvblwvdm5kLmdvb2dsZS1hcHBzLnNwcmVhZHNoZWV0IiwKICAgICAgImlkIiA6ICIxZEtjQWJic3Y2UHJMYWdvTDJEX3I2IiwKICAgICAgImtpbmQiIDogImRyaXZlI2ZpbGUiLAogICAgICAibmFtZSIgOiAiMjAwNiB0YXggZGVkdWN0aW9ucyIsCiAgICAgICJ0cmFzaGVkIiA6IGZhbHNlCiAgICB9LAogICAgewogICAgICAibWltZVR5cGUiIDogImFwcGxpY2F0aW9uXC92bmQuZ29vZ2xlLWFwcHMuZG9jdW1lbnQiLAogICAgICAiaWQiIDogIjFNMFhZakdoRWJZMUJ6SHM1c3JPcFEiLAogICAgICAia2luZCIgOiAiZHJpdmUjZmlsZSIsCiAgICAgICJuYW1lIiA6ICJBbm90aGVyIGRvYyIsCiAgICAgICJ0cmFzaGVkIiA6IGZhbHNlCiAgICB9CiAgXQp9Cg==\","\ + " \"NoPayloadsBatch.response.txt\": \"LS1iYXRjaF9oWExfM19VbkxzUV9BQVlXNmtRNExZVQ0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi9odHRwDQpDb250ZW50LUlEOiByZXNwb25zZS1ndGxyXzM5DQoNCkhUVFAvMS4xIDIwNCBObyBDb250ZW50DQpEYXRlOiBXZWQsIDI0IEp1biAyMDIwIDA5OjIxOjI0IEdNVA0KDQoNCi0tYmF0Y2hfaFhMXzNfVW5Mc1FfQUFZVzZrUTRMWVUNCkNvbnRlbnQtVHlwZTogYXBwbGljYXRpb24vaHR0cA0KQ29udGVudC1JRDogcmVzcG9uc2UtZ3Rscl80MA0KDQpIVFRQLzEuMSAyMDQgTm8gQ29udGVudA0KDQoNCi0tYmF0Y2hfaFhMXzNfVW5Mc1FfQUFZVzZrUTRMWVUNCg==\""\ "}"; static dispatch_once_t onceToken; @@ -1712,6 +1716,37 @@ - (void)performBatchQueryTestSkippingAuthorization:(BOOL)shouldSkipAuthorization expectedExpirations:0]; } +- (void)testParsingMinimalBatchReplies { + // Deletes in a batch have no payload, ensure parsing works as expected. + + NSData *responseData = + [[self class] dataForTestFileName:@"NoPayloadsBatch.response.txt"];; + XCTAssertNotNil(responseData); + + GTLRService *service = [self driveServiceForTest]; + + NSArray *mimeParts = + [GTMMIMEDocument MIMEPartsWithBoundary:@"batch_hXL_3_UnLsQ_AAYW6kQ4LYU" + data:responseData]; + XCTAssertNotNil(mimeParts); + XCTAssertEqual(mimeParts.count, 2U); + + GTLRBatchResponsePart *part0 = [service responsePartWithMIMEPart:mimeParts[0]]; + XCTAssertNotNil(part0); + XCTAssertEqual(part0.statusCode, 204); + XCTAssertEqualObjects(part0.statusString, @"No Content"); + XCTAssertEqual(part0.headers.count, 1U); + XCTAssertEqualObjects(part0.headers[@"Date"], @"Wed, 24 Jun 2020 09:21:24 GMT"); + XCTAssertNil(part0.parseError); + + GTLRBatchResponsePart *part1 = [service responsePartWithMIMEPart:mimeParts[1]]; + XCTAssertNotNil(part1); + XCTAssertEqual(part1.statusCode, 204); + XCTAssertEqualObjects(part1.statusString, @"No Content"); + XCTAssertEqual(part1.headers.count, 0U); + XCTAssertNil(part1.parseError); +} + - (GTMSessionFetcherTestBlock)fetcherTestBlockForBatchPaging { __block int pageCounter = 0;