Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle parsing batch replies which minimal responses. #379

Merged
merged 1 commit into from
Jun 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 12 additions & 14 deletions Source/GTLRCore.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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, ); }; };
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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 = "<group>"; };
4F3E9C8E1C6C32B7004192DE /* Drive1BatchPaging2.response.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Drive1BatchPaging2.response.txt; path = Tests/Data/Drive1BatchPaging2.response.txt; sourceTree = "<group>"; };
4F3E9C8F1C6C32B7004192DE /* Drive1BatchPaging3.response.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Drive1BatchPaging3.response.txt; path = Tests/Data/Drive1BatchPaging3.response.txt; sourceTree = "<group>"; };
4F6F2E701C615492001065A5 /* Drive1BatchCorrupt.response.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Drive1BatchCorrupt.response.txt; path = Tests/Data/Drive1BatchCorrupt.response.txt; sourceTree = "<group>"; };
4F6F2E771C6154AA001065A5 /* Drive1Corrupt.response.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Drive1Corrupt.response.txt; path = Tests/Data/Drive1Corrupt.response.txt; sourceTree = "<group>"; };
4F934E651512712100C4EA34 /* GTLRBase64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTLRBase64.h; path = Utilities/GTLRBase64.h; sourceTree = "<group>"; };
4F934E661512712100C4EA34 /* GTLRBase64.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTLRBase64.m; path = Utilities/GTLRBase64.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -514,6 +516,7 @@
F4D9D51D1D832DB5000EBBED /* GTLRDuration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTLRDuration.h; path = Objects/GTLRDuration.h; sourceTree = "<group>"; };
F4D9D51E1D832DB5000EBBED /* GTLRDuration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTLRDuration.m; path = Objects/GTLRDuration.m; sourceTree = "<group>"; };
F4D9D5251D833D5D000EBBED /* GTLRDurationTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTLRDurationTest.m; path = Tests/GTLRDurationTest.m; sourceTree = "<group>"; };
F4F2CFD924A6530900F1B3A7 /* NoPayloadsBatch.response.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = NoPayloadsBatch.response.txt; path = Tests/Data/NoPayloadsBatch.response.txt; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -615,7 +618,6 @@
089C1671FE841209C02AAC07 /* Frameworks and Libraries */,
19C28FB8FE9D52D311CA2CBB /* Products */,
F4A872741DAD1ED800D69E09 /* Frameworks */,
34AF73421FB4E3C80022335F /* Recovered References */,
);
name = GoogleBundle;
sourceTree = "<group>";
Expand Down Expand Up @@ -678,14 +680,6 @@
name = "GTLR Source";
sourceTree = "<group>";
};
34AF73421FB4E3C80022335F /* Recovered References */ = {
isa = PBXGroup;
children = (
4F6F2E701C615492001065A5 /* Drive1BatchCorrupt.response.txt */,
);
name = "Recovered References";
sourceTree = "<group>";
};
4F346DD20D7500C9006033E0 /* Common */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -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 */,
);
Expand Down Expand Up @@ -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 */,
Expand All @@ -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;
Expand All @@ -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 */,
Expand All @@ -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;
Expand All @@ -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 */,
Expand Down
44 changes: 27 additions & 17 deletions Source/Objects/GTLRService.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
}
}

Expand Down
16 changes: 16 additions & 0 deletions Source/Tests/Data/NoPayloadsBatch.response.txt
Original file line number Diff line number Diff line change
@@ -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
37 changes: 36 additions & 1 deletion Source/Tests/GTLRServiceTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down