libs-base/Tests/base/NSURLConnection/Helpers/NSURLConnectionTest.m

785 lines
18 KiB
Mathematica
Raw Normal View History

/*
* Author: Sergei Golovin <Golovin.SV@gmail.com>
*/
#import "NSURLConnectionTest.h"
/* the runloop's time slice */
#define TIMING 0.1
/* the max duration of a test */
#define MAXDURATION 3.0
@interface NSURLConnectionTest (Private)
/**
* The method tries to guess the request which the test case should make.
* It recognizes as an argument an NSDictionary with custom variable parts
* of connecting process (see the class's description). It also can be supplied
* with the exact request which must be made during test execution.
* The result of the method is stored in the ivar _request.
*/
- (void)_makeRequest:(id)extra;
/**
* The method analyzes the ivar _request and tries to guess the execution path
* inferring from it the reference flag set. If there is a deviation from the guessed
* execution path the caller can supply NSDictionary as the argument 'extra' with
* the key 'ReferenceFlags' is set to have a custom NSDictionary as a value explicitly
* setting the particular flags (see the class's description).
*/
- (void)_makeReferenceFlags:(id)extra;
@end /* NSURLConnectionTest (Private) */
@implementation NSURLConnectionTest
+ (Class)testWebServerClass
{
return [TestWebServer class];
}
/* The flag map connects flags's string names with flags... used to log actions */
static NSMapTable *_flagMap = nil;
+ (void)initialize
{
if(nil == _flagMap)
{
_flagMap = NSCreateMapTable(NSObjectMapKeyCallBacks,
NSIntegerMapValueCallBacks,
0);
NSMapInsert(_flagMap, @"SENTREQUEST", (void *)1);
NSMapInsert(_flagMap, @"AUTHORIZED", (void *)2);
NSMapInsert(_flagMap, @"NOTAUTHORIZED", (void *)4);
NSMapInsert(_flagMap, @"GOTUNAUTHORIZED", (void *)8);
NSMapInsert(_flagMap, @"GOTREQUEST", (void *)16);
NSMapInsert(_flagMap, @"SENTRESPONSE", (void *)32);
NSMapInsert(_flagMap, @"GOTRESPONSE", (void *)64);
NSMapInsert(_flagMap, @"GOTCONTENT", (void *)128);
NSMapInsert(_flagMap, @"GOTFINISH", (void *)256);
NSMapInsert(_flagMap, @"GOTFAIL", (void *)512);
NSMapInsert(_flagMap, @"GOTREDIRECT", (void *)1024);
}
}
- (id)init
{
if((self = [super init]) != nil)
{
_conn = nil;
_error = nil;
_server = nil;
_auxServer = nil;
_received = nil;
_request = nil;
_redirectRequest = nil;
_expectedStatusCode = 204;
_expectedContent = nil;
}
return self;
}
// super's -[dealloc] calls the -[tearDownTest:]
// so place clearing there
- (void)setUpTest:(id)extra
{
[super setUpTest: extra];
[self _makeRequest: extra];
[self _makeReferenceFlags: extra];
_received = [NSMutableData new];
}
- (void)startTest:(id)extra
{
CREATE_AUTORELEASE_POOL(arp);
NSTimeInterval duration = 0.0;
if(YES == _debug)
{
NSLog(@"%@: started with request:\n%@", self, _request);
[self logFlags];
}
_conn = [[NSURLConnection alloc] initWithRequest: _request
delegate: self];
while(!_done && !_failed)
{
[[NSRunLoop currentRunLoop]
runUntilDate: [NSDate dateWithTimeIntervalSinceNow: TIMING]];
duration += TIMING;
if(duration >= MAXDURATION)
{
_failed = YES;
}
}
if(YES == _debug)
{
NSLog(@"%@: stopped with request:\n%@", self, _request);
[self logFlags];
}
DESTROY(arp);
}
- (void)tearDownTest:(id)extra
{
BOOL isToStop = YES; // whether to stop the main TestWebServer instance
BOOL isToStopAux = YES; // whether to stop the auxilliary TestWebServer instance
if([_extra isKindOfClass: [NSDictionary class]])
{
NSDictionary *d = _extra;
isToStop = ([d objectForKey: @"Instance"] == nil);
isToStopAux = ([d objectForKey: @"AuxInstance"] == nil &&
[[d objectForKey: @"IsAuxilliary"] isEqualToString: @"YES"]);
}
if(isToStop)
{
if(nil != _server)
{
[_server stop];
}
}
if(isToStopAux)
{
if(nil != _auxServer)
{
[_auxServer stop];
}
}
DESTROY(_server);
DESTROY(_auxServer);
DESTROY(_received);
DESTROY(_request);
DESTROY(_redirectRequest);
DESTROY(_expectedContent);
[_conn cancel];
DESTROY(_conn);
DESTROY(_error);
[super tearDownTest: extra];
}
- (BOOL)isSuccess
{
BOOL ret = [super isSuccess];
if(!ret)
{
/* log the flags that differ */
NSString *key;
NSUInteger flag;
NSMapEnumerator en = NSEnumerateMapTable(_flagMap);
while(NSNextMapEnumeratorPair(&en, (void **)&key, (void **)&flag))
{
if([self isReferenceFlagSet: flag] != [self isFlagSet: flag])
{
NSLog(@"ERR: %@", key);
}
}
NSEndMapTableEnumeration(&en);
if(_failed)
{
NSLog(@"FAILED for unknown reason possibly not related to the test (e.g. a timer has expired)");
}
}
return ret;
}
- (void)logFlags
{
NSString *value;
NSString *key;
NSUInteger flag;
NSMapEnumerator en = NSEnumerateMapTable(_flagMap);
while(NSNextMapEnumeratorPair(&en, (void **)&key, (void **)&flag))
{
if([self isFlagSet: flag])
{
value = @"YES";
}
else
{
value = @"NO";
}
NSLog(@"ACTUAL: %@ -> %@", key, value);
if([self isReferenceFlagSet: flag])
{
value = @"YES";
}
else
{
value = @"NO";
}
NSLog(@"REFERENCE: %@ -> %@", key, value);
}
NSEndMapTableEnumeration(&en);
}
- (NSError *)error
{
return _error;
}
/* TestWebServerDelegate */
- (void)handler:(id)handler
gotRequest:(GSMimeDocument *)request
with:(TestWebServer *)server
{
NSString *method = [_request HTTPMethod];
NSData *content = [request convertToData];
[self setFlags: SENTREQUEST];
if(YES == _debug)
{
NSLog(@"%@: set SENTREQUEST (-[%@])", self, NSStringFromSelector(_cmd));
}
// TODO: more comparisons of _request and request
if([method isEqualToString: @"POST"] ||
[method isEqualToString: @"PUT"])
{
if([content isEqualToData: [_request HTTPBody]])
{
[self setFlags: GOTREQUEST];
if(YES == _debug)
{
NSLog(@"%@: set GOTREQUEST (-[%@])", self, NSStringFromSelector(_cmd));
}
}
}
else
{
[self setFlags: GOTREQUEST];
if(YES == _debug)
{
NSLog(@"%@: set GOTREQUEST (-[%@])", self, NSStringFromSelector(_cmd));
}
}
}
- (void)handler:(id)handler
willSendUnauthorized:(GSMimeDocument *)response
with:(TestWebServer *)server
{
[self setFlags: NOTAUTHORIZED];
if(YES == _debug)
{
NSLog(@"%@: set NOTAUTHORIZED (-[%@])", self, NSStringFromSelector(_cmd));
}
}
- (void)handler:(id)handler
gotAuthorized:(GSMimeDocument *)request
with:(TestWebServer *)server
{
[self setFlags: AUTHORIZED];
if(YES == _debug)
{
NSLog(@"%@: set AUTHORIZED (-[%@])", self, NSStringFromSelector(_cmd));
}
}
- (void)handler:(id)handler
willSend:(GSMimeDocument *)response
with:(TestWebServer *)server
{
[self setFlags: SENTRESPONSE];
if(YES == _debug)
{
NSLog(@"%@: set SENTRESPONSE (-[%@])", self, NSStringFromSelector(_cmd));
}
}
- (void)timeoutExceededByHandler:(id)handler
{
_failed = YES;
}
/* end of TestWebServerDelegate */
/* NSURLConnectionDelegate */
- (NSURLRequest *)connection:(NSURLConnection *)connection
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)redirectResponse
{
if([redirectResponse isKindOfClass: [NSHTTPURLResponse class]])
{
if([(NSHTTPURLResponse *)redirectResponse statusCode] == _redirectStatusCode)
{
[self setFlags: GOTREDIRECT];
if(YES == _debug)
{
NSLog(@"%@: set GOTREDIRECT (-[%@])", self, NSStringFromSelector(_cmd));
}
}
}
else
{
[self setFlags: GOTREDIRECT];
if(YES == _debug)
{
NSLog(@"%@: set GOTREDIRECT (-[%@])", self, NSStringFromSelector(_cmd));
}
}
return _redirectRequest;
}
- (void)connection:(NSURLConnection *)connection
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
[self setFlags: GOTUNAUTHORIZED];
if(YES == _debug)
{
NSLog(@"%@: set GOTUNAUTHORIZED (-[%@])", self, NSStringFromSelector(_cmd));
}
if([challenge previousFailureCount] == 0)
{
NSURLCredential *cred;
// TODO: _auxServer?
NSString *login = [_server login];
NSString *password = [_server password];
if(nil == login)
{
if([_extra isKindOfClass: [NSDictionary class]])
{
login = [(NSDictionary *)_extra objectForKey: @"Login"];
}
}
if(nil == password)
{
if([_extra isKindOfClass: [NSDictionary class]])
{
password = [(NSDictionary *)_extra objectForKey: @"Password"];
}
}
cred = [NSURLCredential credentialWithUser: login
password: password
persistence: NSURLCredentialPersistenceNone];
[[challenge sender] useCredential: cred
forAuthenticationChallenge: challenge];
}
else
{
[[challenge sender] cancelAuthenticationChallenge: challenge];
}
}
- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response
{
if(YES == _debug)
{
if([response isKindOfClass: [NSHTTPURLResponse class]])
{
NSLog(@"%@: received response (-[%@]):\nStatus Code: %u",
self, NSStringFromSelector(_cmd), [(NSHTTPURLResponse *)response statusCode]);
}
}
if([response isKindOfClass: [NSHTTPURLResponse class]])
{
if([(NSHTTPURLResponse *)response statusCode] == _expectedStatusCode)
{
[self setFlags: GOTRESPONSE];
if(YES == _debug)
{
NSLog(@"%@: set GOTRESPONSE (-[%@])", self, NSStringFromSelector(_cmd));
}
}
}
else
{
[self setFlags: GOTRESPONSE];
if(YES == _debug)
{
NSLog(@"%@: set GOTRESPONSE (-[%@])", self, NSStringFromSelector(_cmd));
}
}
}
- (void)connection:(NSURLConnection *)connection
didReceiveData:(NSData *)data
{
[_received appendData: data];
if(YES == _debug)
{
NSLog(@"%@: received data '%@' (-[%@])", self, data, NSStringFromSelector(_cmd));
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if(nil != _expectedContent &&
[_received isEqualToData: _expectedContent])
{
[self setFlags: GOTCONTENT];
if(YES == _debug)
{
NSLog(@"%@: set GOTCONTENT (-[%@])", self, NSStringFromSelector(_cmd));
}
}
[self setFlags: GOTFINISH];
if(YES == _debug)
{
NSLog(@"%@: set GOTFINISH (-[%@])", self, NSStringFromSelector(_cmd));
}
_done = YES;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
ASSIGN(_error, error);
/* if((nil != _expectedContent &&
[_received isEqualToData: _expectedContent]) ||
(nil == _expectedContent && [_received length] == 0))
{
[self setFlags: GOTCONTENT];
}
*/
[self setFlags: GOTFAIL];
if(YES == _debug)
{
NSLog(@"%@: set GOTFAIL (-[%@])", self, NSStringFromSelector(_cmd));
NSLog(@"%@: error %@", self, error);
}
_done = YES;
}
/* end of NSURLConnectionDelegate */
@end /* NSURLConnectionTest */
@implementation NSURLConnectionTest (Private)
- (void)_makeRequest:(id)extra
{
BOOL isOwnServer = YES;
BOOL isOwnAuxServer = NO;
NSString *tmp;
BOOL isDetached = NO;
TestWebServer *instance = nil;
TestWebServer *auxInstance = nil;
NSString *protocol = nil;
NSString *address = nil;
NSString *auxPort = nil;
NSString *port = nil;
// NSString *login = nil;
// NSString *password = nil;
NSString *path = nil;
NSString *redirectPath = nil;
NSString *statusCode = nil;
NSString *redirectCode;
NSString *method = nil;
id payload = nil;
id content = nil;
NSURL *url;
NSDictionary *d = nil;
if([extra isKindOfClass: [NSDictionary class]])
{
d = extra;
instance = [d objectForKey: @"Instance"];
if(nil != instance)
{
NSString *value;
protocol = [instance isSecure] ? @"https" : @"http";
if((value = [d objectForKey: @"Protocol"]) == nil ||
![[value lowercaseString] isEqualToString: protocol])
{
d = [d mutableCopy];
[(NSMutableDictionary *)d setObject: protocol forKey: @"Protocol"];
[d autorelease];
}
address = [instance address];
port = [instance port];
ASSIGN(_server, instance);
[_server setDelegate: self];
[_server setDebug: _debug];
isOwnServer = NO;
}
else
{
protocol = [[d objectForKey: @"Protocol"] lowercaseString];
address = [d objectForKey: @"Address"];
port = [d objectForKey: @"Port"];
}
auxInstance = [d objectForKey: @"AuxInstance"];
if(nil != auxInstance)
{
auxPort = [auxInstance port];
ASSIGN(_auxServer, auxInstance);
[_auxServer setDelegate: self];
[_auxServer setDebug: _debug];
isOwnAuxServer = NO;
}
if(isOwnServer)
{
if((tmp = [d objectForKey: @"IsDetached"]) != nil)
{
if([tmp isEqualToString: @"YES"])
{
isDetached = YES;
}
else
{
isDetached = NO;
}
}
}
isOwnAuxServer = [[d objectForKey: @"IsAuxilliary"] isEqualToString: @"YES"] &&
nil == _auxServer;
if(isOwnAuxServer)
{
auxPort = [d objectForKey: @"AuxPort"];
}
path = [d objectForKey: @"Path"];
if(isOwnAuxServer || nil != _auxServer)
{
redirectPath = [d objectForKey: @"RedirectPath"];
}
statusCode = [d objectForKey: @"StatusCode"];
// TODO: conditions?
redirectCode = [d objectForKey: @"RedirectCode"];
method = [d objectForKey: @"Method"];
payload = [d objectForKey: @"Payload"];
content = [d objectForKey: @"Content"];
} // Is extra NSDictionary?
else if([extra isKindOfClass: [NSURLRequest class]])
{
ASSIGN(_request, extra);
}
if(nil == _request)
{
if(nil == protocol)
{
protocol = @"http";
}
if(nil == address)
{
address = @"localhost";
}
if(nil == port)
{
port = @"54321";
}
if(nil == auxPort)
{
auxPort = @"54322";
}
if(nil == path)
{
path = @"/";
}
if((isOwnAuxServer || nil != _auxServer) && nil == redirectPath)
{
redirectPath = path;
}
if(nil != redirectPath && nil == redirectCode)
{
_redirectStatusCode = 301;
}
if(nil == statusCode)
{
_expectedStatusCode = 204;
}
else
{
_expectedStatusCode = [statusCode intValue];
if(_expectedStatusCode == 0)
{
[NSException raise: NSInvalidArgumentException
format: @"Invalid expected 'StatusCode' supplied %@", statusCode];
}
}
if(nil == redirectCode)
{
_redirectStatusCode = 301;
}
else
{
_redirectStatusCode = [redirectCode intValue];
if(_redirectStatusCode == 0)
{
[NSException raise: NSInvalidArgumentException
format: @"Invalid expected 'RedirectCode' supplied %@", redirectCode];
}
}
if(nil == method)
{
method = @"GET";
}
if(![path hasPrefix: @"/"])
{
// path MUST begin with '/'
path = [@"/" stringByAppendingString: path];
}
if(![redirectPath hasPrefix: @"/"])
{
// path MUST begin with '/'
redirectPath = [@"/" stringByAppendingString: redirectPath];
}
url = [NSURL URLWithString:
[NSString stringWithFormat: @"%@://%@:%@%@", protocol, address, auxPort, redirectPath]];
_redirectRequest = [NSMutableURLRequest requestWithURL: url];
RETAIN(_redirectRequest);
url = [NSURL URLWithString:
[NSString stringWithFormat: @"%@://%@:%@%@", protocol, address, port, path]];
_request = [NSMutableURLRequest requestWithURL: url];
RETAIN(_request);
[(NSMutableURLRequest *)_request setHTTPMethod: method];
if(nil != payload)
{
if([payload isKindOfClass: [NSString class]])
{
payload = [payload dataUsingEncoding: NSUTF8StringEncoding];
}
if(![payload isKindOfClass: [NSData class]])
{
[NSException raise: NSInvalidArgumentException
format: @"invalid payload"];
}
[(NSMutableURLRequest *)_request setHTTPBody: payload];
}
if(nil != content)
{
if([content isKindOfClass: [NSString class]])
{
content = [content dataUsingEncoding: NSUTF8StringEncoding];
}
if(![content isKindOfClass: [NSData class]])
{
[NSException raise: NSInvalidArgumentException
format: @"invalid content"];
}
ASSIGN(_expectedContent, content);
}
if(nil != _expectedContent && nil == statusCode)
{
_expectedStatusCode = 200;
}
}
if(isOwnServer)
{
_server = [[TestWebServer alloc] initWithAddress: address
port: port
mode: isDetached
extra: d];
[_server setDebug: _debug];
[_server setDelegate: self];
[_server start: d];
}
if(isOwnAuxServer)
{
_auxServer = [[TestWebServer alloc] initWithAddress: address
port: auxPort
mode: isDetached
extra: d];
[_auxServer setDebug: _debug];
[_auxServer setDelegate: self];
[_auxServer start: d];
}
}
- (void)_makeReferenceFlags:(id)extra
{
// trying to guess the execution path
// and to infer the reference flag set
if(nil != _request)
{
// default reference set
[self setReferenceFlags: SENTREQUEST];
[self setReferenceFlags: NOTAUTHORIZED];
[self setReferenceFlags: AUTHORIZED];
[self setReferenceFlags: GOTUNAUTHORIZED];
[self setReferenceFlags: GOTREQUEST];
[self setReferenceFlags: SENTRESPONSE];
[self setReferenceFlags: GOTRESPONSE];
if(nil != _expectedContent)
{
[self setReferenceFlags: GOTCONTENT];
}
[self setReferenceFlags: GOTFINISH];
}
else
{
[NSException raise: NSInvalidArgumentException
format: @"no request"];
}
if([extra isKindOfClass: [NSDictionary class]])
{
// make correction in the reference flag set
NSDictionary *d = extra;
if((d = [d objectForKey: @"ReferenceFlags"]) != nil)
{
NSEnumerator *en = [d keyEnumerator];
NSString *key;
NSString *value;
SEL sel;
while((key = [en nextObject]) != nil)
{
value = [d objectForKey: key];
NSString *originalKey = nil;
NSUInteger flag = NORESULTS;
// NSUInteger flag = (NSUInteger)NSMapGet(_flagMap, key);
if(NSMapMember(_flagMap, key, (void **)&originalKey, (void **)&flag))
{
if([value isEqualToString: @"YES"])
{
sel = @selector(setReferenceFlags:);
}
else
{
sel = @selector(unsetReferenceFlags:);
}
[self performSelector: sel withObject: (void *)flag];
}
else
{
[NSException raise: NSInternalInconsistencyException
format: @"flag codes corrupted (key %@)", key];
}
}
}
}
}
@end /* NSURLConnectionTest (Private) */