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

752 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
2019-08-12 15:37:38 +00:00
+ (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];
2019-08-12 15:37:38 +00:00
NSMutableSet *s = [[NSProcessInfo processInfo] debugSet];
[s addObject: @"NSURLConnection"];
[s addObject: @"NSURLProtocol"];
[s addObject: @"NSStream"];
[s addObject: @"NSRunLoop"];
}
_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);
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);
}
2019-08-12 15:37:38 +00:00
- (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];
2024-03-07 04:38:02 +00:00
NSDebugLog(@"%@: 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];
2024-03-07 04:38:02 +00:00
NSDebugLog(@"%@: set GOTREQUEST (-[%@])", self, NSStringFromSelector(_cmd));
}
}
else
{
[self setFlags: GOTREQUEST];
2024-03-07 04:38:02 +00:00
NSDebugLog(@"%@: set GOTREQUEST (-[%@])", self, NSStringFromSelector(_cmd));
}
}
- (void)handler:(id)handler
willSendUnauthorized:(GSMimeDocument *)response
with:(TestWebServer *)server
{
[self setFlags: NOTAUTHORIZED];
2024-03-07 04:38:02 +00:00
NSDebugLog(@"%@: set NOTAUTHORIZED (-[%@])", self, NSStringFromSelector(_cmd));
}
- (void)handler:(id)handler
gotAuthorized:(GSMimeDocument *)request
with:(TestWebServer *)server
{
[self setFlags: AUTHORIZED];
2024-03-07 04:38:02 +00:00
NSDebugLog(@"%@: set AUTHORIZED (-[%@])", self, NSStringFromSelector(_cmd));
}
- (void)handler:(id)handler
willSend:(GSMimeDocument *)response
with:(TestWebServer *)server
{
[self setFlags: SENTRESPONSE];
2024-03-07 04:38:02 +00:00
NSDebugLog(@"%@: 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];
2024-03-07 04:38:02 +00:00
NSDebugLog(@"%@: set GOTREDIRECT (-[%@])", self, NSStringFromSelector(_cmd));
}
}
else
{
[self setFlags: GOTREDIRECT];
2024-03-07 04:38:02 +00:00
NSDebugLog(@"%@: set GOTREDIRECT (-[%@])", self, NSStringFromSelector(_cmd));
}
return _redirectRequest;
}
- (void)connection:(NSURLConnection *)connection
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
[self setFlags: GOTUNAUTHORIZED];
2024-03-07 04:38:02 +00:00
NSDebugLog(@"%@: 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: %i",
self, NSStringFromSelector(_cmd),
(int)[(NSHTTPURLResponse *)response statusCode]);
}
}
if ([response isKindOfClass: [NSHTTPURLResponse class]])
{
if ([(NSHTTPURLResponse *)response statusCode] == _expectedStatusCode)
{
[self setFlags: GOTRESPONSE];
2024-03-07 04:38:02 +00:00
NSDebugLog(@"%@: set GOTRESPONSE (-[%@])", self, NSStringFromSelector(_cmd));
}
}
else
{
[self setFlags: GOTRESPONSE];
2024-03-07 04:38:02 +00:00
NSDebugLog(@"%@: set GOTRESPONSE (-[%@])", self, NSStringFromSelector(_cmd));
}
}
- (void)connection:(NSURLConnection *)connection
didReceiveData:(NSData *)data
{
[_received appendData: data];
2024-03-07 04:38:02 +00:00
NSDebugLog(@"%@: received data '%@' (-[%@])", self, data, NSStringFromSelector(_cmd));
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if (nil != _expectedContent &&
[_received isEqualToData: _expectedContent])
{
[self setFlags: GOTCONTENT];
2024-03-07 04:38:02 +00:00
NSDebugLog(@"%@: set GOTCONTENT (-[%@])", self, NSStringFromSelector(_cmd));
}
[self setFlags: GOTFINISH];
2024-03-07 04:38:02 +00:00
NSDebugLog(@"%@: 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];
2024-03-07 04:38:02 +00:00
NSDebugLog(@"%@: set GOTFAIL (-[%@])", self, NSStringFromSelector(_cmd));
NSDebugLog(@"%@: 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 = nil;
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"];
AUTORELEASE(d);
}
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 = @"1234";
}
if (nil == auxPort)
{
auxPort = @"1235";
}
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) */