Add tests for CDVFileTransfer.
Project: http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/commit/dea6eb24 Tree: http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/tree/dea6eb24 Diff: http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/diff/dea6eb24 Branch: refs/heads/master Commit: dea6eb24d6fdf37c0fa30b7194f91b4c100b0f49 Parents: e642925 Author: Andrew Grieve <[email protected]> Authored: Tue Jul 10 23:32:10 2012 -0400 Committer: Shazron Abdullah <[email protected]> Committed: Wed Jul 11 14:23:01 2012 -0700 ---------------------------------------------------------------------- CordovaLib/Classes/CDVFileTransfer.h | 5 +- CordovaLib/Classes/CDVFileTransfer.m | 43 +++-- CordovaLib/CordovaLibTests/CDVFileTransferTests.m | 171 ++++++++++++++++ 3 files changed, 200 insertions(+), 19 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/dea6eb24/CordovaLib/Classes/CDVFileTransfer.h ---------------------------------------------------------------------- diff --git a/CordovaLib/Classes/CDVFileTransfer.h b/CordovaLib/Classes/CDVFileTransfer.h index 4289074..54a37b3 100644 --- a/CordovaLib/Classes/CDVFileTransfer.h +++ b/CordovaLib/Classes/CDVFileTransfer.h @@ -34,6 +34,9 @@ enum CDVFileTransferDirection { }; typedef int CDVFileTransferDirection; +// Magic value within the options dict used to set a cookie. +extern NSString* const kOptionsKeyCookie; + @interface CDVFileTransfer : CDVPlugin { } @@ -43,7 +46,7 @@ typedef int CDVFileTransferDirection; - (NSString*) escapePathComponentForUrlString:(NSString*)urlString; // Visible for testing. -- (NSURLRequest*) requestForUpload:(NSArray*)arguments withDict:(NSDictionary*)options; +- (NSURLRequest*) requestForUpload:(NSArray*)arguments withDict:(NSDictionary*)options fileData:(NSData*)fileData; -(NSMutableDictionary*) createFileTransferError:(int)code AndSource:(NSString*)source AndTarget:(NSString*)target; -(NSMutableDictionary*) createFileTransferError:(int)code http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/dea6eb24/CordovaLib/Classes/CDVFileTransfer.m ---------------------------------------------------------------------- diff --git a/CordovaLib/Classes/CDVFileTransfer.m b/CordovaLib/Classes/CDVFileTransfer.m index fb3a723..b7c16d0 100644 --- a/CordovaLib/Classes/CDVFileTransfer.m +++ b/CordovaLib/Classes/CDVFileTransfer.m @@ -22,13 +22,16 @@ #include <CFNetwork/CFNetwork.h> @interface CDVFileTransfer () -- (CDVFileTransferDelegate*) delegateForUpload:(NSArray*)arguments; +// Creates a delegate to handle an upload. +- (CDVFileTransferDelegate*)delegateForUpload:(NSArray*)arguments; +// Creates an NSData* for the file for the given upload arguments. +- (NSData*)fileDataForUploadArguments:(NSArray*)arguments; @end // Buffer size to use for streaming uploads. static const NSUInteger kStreamBufferSize = 32768; // Magic value within the options dict used to set a cookie. -static NSString* kOptionsKeyCookie = @"__cookie"; +NSString* const kOptionsKeyCookie = @"__cookie"; // Writes the given data to the stream in a blocking way. // If successful, returns bytesToWrite. @@ -79,7 +82,7 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) { return [urlString stringByAppendingString:pathComponent]; } -- (NSURLRequest*) requestForUpload:(NSArray*)arguments withDict:(NSDictionary*)options { +- (NSURLRequest*) requestForUpload:(NSArray*)arguments withDict:(NSDictionary*)options fileData:(NSData*)fileData { NSString* callbackId = [arguments objectAtIndex:0]; // arguments order from js: [filePath, server, fileKey, fileName, mimeType, params, debug, chunkedMode] @@ -101,27 +104,17 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) { chunkedMode = NO; } - // Extract the path part out of a file: URL. - NSString* filePath = [target hasPrefix:@"/"] ? [[target copy] autorelease] : [[NSURL URLWithString:target] path]; - CDVPluginResult* result = nil; CDVFileTransferError errorCode = 0; NSURL *url = [NSURL URLWithString:[self escapePathComponentForUrlString:server]]; - NSData *fileData = nil; if (!url) { errorCode = INVALID_URL_ERR; NSLog(@"File Transfer Error: Invalid server URL %@", server); - } else { - NSError *err = nil; - // Memory map the file so that it can be read efficiently even if it is large. - fileData = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:&err]; - if (!fileData) { - errorCode = FILE_NOT_FOUND_ERR; - NSLog(@"File Transfer Error: Could not read file data %@", filePath); - } + } else if (!fileData) { + errorCode = FILE_NOT_FOUND_ERR; } if(errorCode > 0) { @@ -208,10 +201,8 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) { DLog(@"fileData length: %d", [fileData length]); NSData *postBodyAfterFile = [[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]; - NSUInteger totalPayloadLength = [postBodyBeforeFile length] + [fileData length] + [postBodyAfterFile length]; [req setValue:[[NSNumber numberWithInteger:totalPayloadLength] stringValue] forHTTPHeaderField:@"Content-Length"]; - if (chunkedMode) { @@ -258,8 +249,24 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) { return delegate; } +- (NSData*) fileDataForUploadArguments:(NSArray*)arguments { + NSString* target = (NSString*)[arguments objectAtIndex:1]; + NSError *err = nil; + // Extract the path part out of a file: URL. + NSString* filePath = [target hasPrefix:@"/"] ? [[target copy] autorelease] : [[NSURL URLWithString:target] path]; + + // Memory map the file so that it can be read efficiently even if it is large. + NSData* fileData = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:&err]; + if (err != nil) { + NSLog(@"Error opening file %@: %@", target, err); + } + return fileData; +} + - (void) upload:(NSArray*)arguments withDict:(NSDictionary*)options { - NSURLRequest* req = [self requestForUpload:arguments withDict:options]; + // fileData and req are split into helper functions to ease the unit testing of delegateForUpload. + NSData* fileData = [self fileDataForUploadArguments:arguments]; + NSURLRequest* req = [self requestForUpload:arguments withDict:options fileData:fileData]; CDVFileTransferDelegate* delegate = [self delegateForUpload:arguments]; [NSURLConnection connectionWithRequest:req delegate:delegate]; } http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/dea6eb24/CordovaLib/CordovaLibTests/CDVFileTransferTests.m ---------------------------------------------------------------------- diff --git a/CordovaLib/CordovaLibTests/CDVFileTransferTests.m b/CordovaLib/CordovaLibTests/CDVFileTransferTests.m new file mode 100644 index 0000000..cfdfa48 --- /dev/null +++ b/CordovaLib/CordovaLibTests/CDVFileTransferTests.m @@ -0,0 +1,171 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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 <SenTestingKit/SenTestingKit.h> + +#import "CDV.h" + +static NSString* const kDummyArgCallbackId = @"cid0"; +static NSString* const kDummyArgFileKey = @"image.jpg"; +static NSString* const kDummyArgTarget = @"/path/to/image.jpg"; +static NSString* const kDummyArgServer = @"http://apache.org"; +static NSString* const kDummyFileContents = @"0123456789"; + +// Reads the given stream and returns the contents as an NSData. +static NSData* readStream(NSInputStream* stream) { + static const NSUInteger kBufferSize = 1024; + + UInt8* buffer = malloc(kBufferSize); + NSMutableData* streamData = [NSMutableData data]; + + [stream open]; + for (;;) { + NSInteger read = [stream read:buffer maxLength:kBufferSize]; + if (read > 0) { + [streamData appendBytes:buffer length:read]; + } else { + break; + } + } + free(buffer); + [stream close]; + return streamData; +} + +@interface CDVFileTransferTests : SenTestCase { + NSMutableArray* _arguments; + CDVFileTransfer* _fileTransfer; + NSData* _dummyFileData; +} +@end + +@implementation CDVFileTransferTests + +- (void)setUp +{ + [super setUp]; + _arguments = [[NSMutableArray alloc] initWithObjects: + kDummyArgCallbackId, kDummyArgTarget, kDummyArgServer, kDummyArgFileKey, + [NSNull null], [NSNull null], [NSNull null], [NSNull null], nil]; + _dummyFileData = [[kDummyFileContents dataUsingEncoding:NSUTF8StringEncoding] retain]; + _fileTransfer = [[CDVFileTransfer alloc] init]; +} + +- (void)tearDown +{ + [_arguments release]; + _arguments = nil; + [_dummyFileData release]; + _dummyFileData = nil; + [_fileTransfer release]; + _fileTransfer = nil; + [super tearDown]; +} + +- (void)setFilePathArg:(NSString*)filePath { + [_arguments replaceObjectAtIndex:1 withObject:filePath]; +} + +- (void)setServerUrlArg:(NSString*)serverUrl { + [_arguments replaceObjectAtIndex:2 withObject:serverUrl]; +} + +- (void)setChunkedModeArg:(BOOL)chunk { + [_arguments replaceObjectAtIndex:7 withObject:[NSNumber numberWithBool:chunk]]; +} + +- (NSURLRequest*)requestForUpload { + return [_fileTransfer requestForUpload:_arguments withDict:nil fileData:_dummyFileData]; +} + +- (void)checkUploadRequest:(NSURLRequest*)request chunked:(BOOL)chunked { + STAssertTrue([@"POST" isEqualToString:[request HTTPMethod]], nil); + NSData* payloadData = nil; + if (chunked) { + STAssertNil([request HTTPBody], nil); + STAssertNotNil([request HTTPBodyStream], nil); + payloadData = readStream([request HTTPBodyStream]); + } else { + STAssertNotNil([request HTTPBody], nil); + STAssertNil([request HTTPBodyStream], nil); + payloadData = [request HTTPBody]; + } + NSUInteger contentLength = [[request valueForHTTPHeaderField:@"Content-Length"] intValue]; + STAssertEquals([payloadData length], contentLength, nil); +} + +- (void)testUpload_invalidServerUrl +{ + [self setServerUrlArg:@"invalid url"]; + STAssertNil([self requestForUpload], nil); +} + +- (void)testUpload_missingFileData +{ + [_dummyFileData release]; + _dummyFileData = nil; + STAssertNil([self requestForUpload], nil); +} + +- (void)testUpload_serverUrlPathEscaping +{ + [self setServerUrlArg:@"http://apache.org/spa ce%"]; + NSURLRequest* request = [self requestForUpload]; + STAssertTrue([[[request URL] absoluteString] isEqualToString:@"http://apache.org/spa%20ce%25"], nil); +} + +- (void)testUpload_nonChunked +{ + [self setChunkedModeArg:NO]; + NSURLRequest* request = [self requestForUpload]; + [self checkUploadRequest:request chunked:NO]; +} + +- (void)testUpload_chunked +{ + // As noted in the implementation, chunked upload is disabled pre 5.0. + if (!IsAtLeastiOSVersion(@"5.0")) { + return; + } + // Chunked is the default. + NSURLRequest* request = [self requestForUpload]; + [self checkUploadRequest:request chunked:YES]; +} + +- (void)testUpload_withOptions +{ + [self setChunkedModeArg:NO]; + NSDictionary* headers = [NSDictionary dictionaryWithObjectsAndKeys:@"val1", @"key1", + @"val2", @"key2", nil]; + NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:@"cookieval", kOptionsKeyCookie, + headers, @"headers", @"val3", @"key3", nil]; + NSURLRequest* request = [_fileTransfer requestForUpload:_arguments withDict:options fileData:_dummyFileData]; + NSString* payload = [[[NSString alloc] initWithData:[request HTTPBody] encoding:NSUTF8StringEncoding] autorelease]; + // Check that headers are properly set. + STAssertTrue([@"val1" isEqualToString:[request valueForHTTPHeaderField:@"key1"]], nil); + STAssertTrue([@"val2" isEqualToString:[request valueForHTTPHeaderField:@"key2"]], nil); + // Check that the cookie is set, and that it is not in the payload like others in the options dict. + STAssertTrue([@"cookieval" isEqualToString:[request valueForHTTPHeaderField:@"Cookie"]], nil); + STAssertEquals([payload rangeOfString:@"cookieval"].length, 0U, nil); + // Check that key3 is in the payload. + STAssertTrue([payload rangeOfString:@"key3"].length > 0, nil); + STAssertTrue([payload rangeOfString:@"val3"].length > 0, nil); +} + +@end
