Add NSURLConnection patch by Sergei Golovin

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@38214 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
rfm 2014-11-29 11:39:38 +00:00
parent a75ab40fec
commit 7ecc487a1b
21 changed files with 4457 additions and 27 deletions

View file

@ -1,3 +1,8 @@
2014-11-29 Sergei Golovin <Golovin.SV@gmail.com>
* Tests/base/NSURLConnection: Test helper tool plus a load of
tests for NSURLConnection.
2014-11-29 Richard Frith-Macdonald <rfm@gnu.org>
* Source/NSScanner.m: Fixup error in scanning doubles with excess

View file

@ -1,27 +0,0 @@
# __GENERATED__ makefile marker
#
include $(GNUSTEP_MAKEFILES)/common.make
-include ../GNUmakefile.super
GNUSTEP_OBJ_DIR=./obj
TEST_TOOL_NAME = basic
ifeq ($(gcov),yes)
ADDITIONAL_OBJCFLAGS += -ftest-coverage -fprofile-arcs
ADDITIONAL_OBJCCFLAGS += -ftest-coverage -fprofile-arcs
ADDITIONAL_LDFLAGS += -ftest-coverage -fprofile-arcs
ADDITIONAL_TOOL_LIBS+=-lgcov
endif
basic_OBJC_FILES=basic.m
-include GNUmakefile.preamble
include $(GNUSTEP_MAKEFILES)/test-tool.make
-include GNUmakefile.postamble
after-clean::
rm -f core core.* *.core tests.log tests.sum oldtests.log oldtests.sum

View file

@ -0,0 +1,20 @@
include $(GNUSTEP_MAKEFILES)/common.make
BUNDLE_NAME = TestConnection
TestConnection_OBJC_FILES = TestCase.m SimpleWebServer.m TestWebServer.m NSURLConnectionTest.m RequestHandler.m
#TestConnection_OBJC_LIBS += -lWebServer -lPerformance
TestConnection_RESOURCE_FILES += testKey.pem testCert.pem
TestConnection_PRINCIPAL_CLASS = NSURLConnectionTest
StatusServer_NEEDS_GUI = NO
TOOL_NAME = testTestWebServer
testTestWebServer_OBJC_FILES += testTestWebServer.m
testTestWebServer_NEEDS_GUI = NO
-include GNUmakefile.preamble
include $(GNUSTEP_MAKEFILES)/bundle.make
include $(GNUSTEP_MAKEFILES)/tool.make
-include GNUmakefile.postamble

View file

@ -0,0 +1,219 @@
/** -*- objc -*-
*
* Author: Sergei Golovin <Golovin.SV@gmail.com>
*
* The class is intended to test the class NSURLConnection. It is designed to start
* a TestWebServer instance and make NSURLConnection to it getting TestWebServerDelegate's
* and NSURLConnection's callbacks. As TestCase's child it has two flag sets, the actual
* one and the reference one (See TestCase.h). It sets/unsets flags of the actual set on
* the execution path at the 'checkpoints' listed below as macros.
*
* The method -[isSuccess] is called when the NSURLConnection finishes (fails). It makes
* a comparison between the two flag sets. The result signifies a success/failure of the
* test.
*
* The test case which the NSURLConnectionTest implements by default is connecting
* to the http://localhost:54321/ whith the HTTP method 'GET'. You can change variable
* parts of process by supplying a custom dictionary to the method -[setUpTest:]
* before the test case is started by a call of the -[startTest:]. The use pattern:
* --------------------------------------------------------------------------
* #import "NSURLConnectionTest.h"
* #import "Testing.h"
*
* NSURLConnectionTest *testCase = [NSURLConnectionTest new];
* // the extra dictionary with test cases's parameters
* NSDictionary *d = [NSDictionary dictionaryWithObjectsAndKeys:
* @"/somepath", @"Path",
* @"POST", @"Method",
* @"https", @"Protocol",
* nil];
* [testCase setUpTest: d];
* [testCase startTest: d];
* PASS([testCase isSuccess], "a diagnostic message about the test");
* [testCase tearDownTest: d];
* DESTROY(testCase);
* --------------------------------------------------------------------------
*
* The method -[setUpTest:] recognises the following variable parts (supplied as key-value
* pairs):
*
* 'Instance'
* 'AuxInstance' - holds an already running main/auxilliary TestWebServer instance.
* If nil the class will run one main instance of TestWebServer
* and no auxilliary one. The key 'IsAuxilliary' with the value 'YES'
* should be supplied to run an own auxilliary instance.
* Useful to run several tests consequently. The test won't stop
* on its own the supplied instance(s). It leaves the calling code
* to make the decision. The auxilliary instance 'AuxInstance' is
* used in tests on redirecting where two web servers are needed.
* Default: nil
* 'IsAuxilliary' - whether the NSURLConnectionTest should run it's own auxilliary
* TestWebServer instance. You must supply YES as a value to run it.
* It will be run with the same parameters (address/protocol/detached)
* as the main one except of the port which is assigned from the value
* of the key 'AuxPort'.
* Default: NO
* 'IsDetached' - whether to run the own(and auxilliary) instance in the detached mode
* (the instance will be run within a detached thread).
* Default: NO
* 'Address' - the address of the remote side.
* Default: localhost
* 'Port' - the port of the remote side.
* Default: 54321
* 'AuxPort' - the port of the auxilliary remote side (where the connection
* to be redirected).
* Default: 54322
* 'Protocol' - the network protocol (supports currently http, https).
* Default: http
* 'Path' - the path of the URL.
* Default: '/'
* 'RedirectPath' - the path where request should be redirected in corresponding tests.
* Default: '/'
* 'StatusCode' - the status code expected from the remote side if the test
* is successful.
* Default: 204
* 'RedirectCode' - the status code expected from the remote side on the connection's first
* stage in a test with redirect.
* Default: 301
* 'Method' - the request's method.
* Default: GET
* 'Payload' - the request's payload. It can be of NSString or of NSData class.
* The class produces NSData from NSString using NSUTF8StringEncoding.
* Default: nil
* 'Content' - the expected content. It can be of NSString or of NSData class.
* The class produces NSData from NSString using NSUTF8StringEncoding.
* Default: nil
* 'ReferenceFlags' - the dictionary to correct the guessed reference flag set.
* Default: nil
* The class tries to infer the reference flag set from
* the test request. If that guessed (default) set is wrong then this
* dictionary allows to correct it.
* The class recognises the following keys:
*
* 'SENTREQUEST'
* 'AUTHORIZED'
* 'NOTAUTHORIZED'
* 'GOTUNAUTHORIZED'
* 'GOTREQUEST'
* 'SENTRESPONSE'
* 'GOTRESPONSE'
* 'GOTCONTENT'
* 'GOTFINISH'
* 'GOTFAIL'
* 'GOTREDIRECT'
*
* the 'YES' as a value means the corresponding reference flag
* must be set, otherwise 'NO' means it must not be set.
*
* The NSURLConnectionTest can raise it's verbosity level by the method call -[setDebug: YES].
* In this case whole connection session will be showed in the log.
*
*/
#import "TestWebServer.h"
#import "TestCase.h"
/* the test's checkpoint list */
/* the request has been sent by the client and reaches the server */
#define SENTREQUEST 1
/* the client's request has been authorized */
#define AUTHORIZED 2
/* the client's request hasn't been authorized */
#define NOTAUTHORIZED 4
/* the client has got Unauthorized response */
#define GOTUNAUTHORIZED 8
/* the server has got a right request */
#define GOTREQUEST 16
/* the server is ready to send the response */
#define SENTRESPONSE 32
/* the client has got the response from the server with valid headers/properties */
#define GOTRESPONSE 64
/* the client has got the expected content from the server's response */
#define GOTCONTENT 128
/* the test's execution has reached client's -[connectionDidFinishLoading:] */
#define GOTFINISH 256
/* the test's execution has reached client's -[connection:didFailWithError:] */
#define GOTFAIL 512
/* the test's execution has reached client's -[connection:willSendRequest:redirectResponse:]*/
#define GOTREDIRECT 1024
/* the end of the test's checkpoint list */
@interface NSURLConnectionTest : TestCase <TestWebServerDelegate>
{
/* the main TestWebServer instance */
TestWebServer *_server;
/* tha auxilliary TestWebServer instance needed in tests on redirecting */
TestWebServer *_auxServer;
/* the custom request (made by the instance or supplied externally) */
NSURLRequest *_request;
/* the redirect request (made by the instance) */
NSURLRequest *_redirectRequest;
/* the data accumulator for the received response's content */
NSMutableData *_received;
/* the expected status code */
NSUInteger _expectedStatusCode;
/* the expected redirect status code of the connection's first stage
* in tests with redirect... the resulting (on the second stage)
* expected status code is stored in the ivar _expectedStatusCode */
NSUInteger _redirectStatusCode;
/* the expected response's content */
NSData *_expectedContent;
/* the connection */
NSURLConnection *_conn;
/* to store the error supplied with the -[connection:didFailWithError:] */
NSError *_error;
}
/**
* Returns a TestWebServer-like class used by this test case class. A descendant can
* return more advanced implementation of TestWebServer's functionality.
*/
+ (Class)testWebServerClass;
+ (void)initialize;
- (id)init;
/* See the super's description */
- (void)setUpTest:(id)extra;
- (void)startTest:(id)extra;
- (void)tearDownTest:(id)extra;
/**
* The only difference from the super's one is issuing of a diagnostic message
* if the test is failed.
*/
- (BOOL)isSuccess;
/**
* Issues log messages describing the actual and reference flag sets' states.
*/
- (void)logFlags;
/**
* Returns the error stored by the -[connection:didFailWithError:].
*/
- (NSError *)error;
/* NSURLConnectionDelegate */
- (NSURLRequest *)connection:(NSURLConnection *)connection
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)redirectResponse;
- (void)connection:(NSURLConnection *)connection
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response;
- (void)connection:(NSURLConnection *)connection
didReceiveData:(NSData *)data;
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
/* end of NSURLConnectionDelegate */
@end /* NSURLConnectionTest */

View file

@ -0,0 +1,784 @@
/*
* 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) */

View file

@ -0,0 +1,29 @@
The directory contains classes useful in testing of web-clients.
They are written mainly with the necessity to test the class NSURLConnection
in mind.
TestCase
NSURLConnectionTest
is a child of TestCase and represents a test of NSURLConnection.
It starts a main instance and (if needed) an auxillary instance
of TestWebServer (or uses externally supplied ones). Then it makes
a custom request to check various functionality of NSURLConnection.
TestWebServer
maintains it's own instance of SimpleWebServer and implements
SimpleWebServer delegate's protocol to dispatch any request
to a corresponding handler. It can call it's delegate during
proceeding of requests.
RequestHandler
TestHandler and it's descendants
handle requests (dispatched by TestWebServer) and produces
a custom response. It also calls it's delegate (using TestWebServer
delegate's protocol) during request handling.
SimpleWebServer
The own implementation of a web server.
The key protocols important for understanding are TestProgress (TestCase.h)
and TestWebServerDelegate (TestWebServer.h). Alternatively the file
NSURLConnectionTest.h briefly describes the whole picture.

View file

@ -0,0 +1,304 @@
/** -*- objc -*-
*
* Author: Sergei Golovin <Golovin.SV@gmail.com>
*
*/
/*
* TODO: there is lack of session handling
*/
#import "TestWebServer.h"
/**
* The protocol RequestHandler describes how a request receiver should interact
* with a request handler.
*/
@protocol RequestHandler
/**
* Does all job needed to prepare for request handling. Returns YES if
* the handler still can proceed the supplied request later.
* The reason this method was introduced is possible session
* handling in the future.
*/
- (BOOL)prehandleRequest:(GSMimeDocument *)request
response:(GSMimeDocument *)response
for:(TestWebServer *)server;
/**
* Makes a custom response. Returns YES if the handler has recognized
* it is it's responsibility to handle the request.
*/
- (BOOL) handleRequest:(GSMimeDocument *)request
response:(GSMimeDocument *)response
for:(TestWebServer *)server;
/**
* Does all job needed to be done after request handling. Returns NO
* if the handler has discovered some inconsistency after handling.
* The reason this method was introduced is possible session
* handling in the future.
*/
- (BOOL)posthandleRequest:(GSMimeDocument *)request
response:(GSMimeDocument *)response
for:(TestWebServer *)server;
@end /* RequestHandler */
/**
* The abstract class TestHandler. Custom handlers must be derived from it.
* The class implements the protocol RequestHandler's methods that are called
* by an instance of TestWebServer. The protocol's method -[handleRequest:response:for:]
* produces a custom response. It also calls it's delegate's callback methods of
* the protocol TestWebServerDelegate (if implemented) during proceeding of a request.
*
* On use of the -[handleRequest:response:for:]
* ---------------------------------------------
*
* The TestHandler's implementation of the -[handleRequest:response:for:] checks
* whether the supplied request is authorized. It returns YES if no processing is
* required (by a child) so the response must be returned intact. If the method
* returns NO then the child MAY process the request on it's own. If the request is
* authorized the method also sets the flag _isAuthorized to YES.
*
* Child's implementation MUST call this super's one as in the following
* example code:
* ------------------------------------------------------------------------
* - (BOOL) handleRequest: (GSMimeDocument*)request
* response: (GSMimeDocument*)response
* for: (TestWebServer *)server
* {
* BOOL ret = [super handleRequest: request response: response for: server];
* if(NO == ret)
* {
* // process on it's own with possible checking _isAuthorized if needed
* ...
* _done = YES;
* ret = YES;
* }
*
* return ret;
* }
* ------------------------------------------------------------------------
*
* The method recognizes the case when the request's path contains '/withoutauth'.
* In this case no authorization is required and the method -[handleRequest:response:for:]
* just returns NO and sets the flag _isAuthorized to YES.
*
*/
@interface TestHandler : NSObject <RequestHandler>
{
/* the debug mode switch */
BOOL _debug;
/* the handler's delegate... NOT RETAINED */
id _delegate;
/* the flag signifies the handler has got an authorized request */
BOOL _isAuthorized;
/* the login for basic authentication */
NSString *_login;
/* the password for basic authentication */
NSString *_password;
/* the flag for successful handling...
can be used in the -[posthandleRequest:response:for:] */
BOOL _done;
}
- (id)init;
- (void)dealloc;
/* getters */
/* end of getters */
/* setters */
/**
* Switches the debug mode on/off.
*/
- (void)setDebug:(BOOL)flag;
/**
* Sets the delegate implementing TestWebServerDelegate protocol.
*/
- (void)setDelegate:(id)delegate;
/**
* Sets the login for requests with authorization.
*/
- (void)setLogin:(NSString *)login;
/**
* Sets the password for requests with authorization.
*/
- (void)setPassword:(NSString *)password;
/* end of setters */
@end /* TestHandler */
/* The handler collection */
/**
* The handler returns the status code 200 for any request.
* The response's content is 'OK'. The status line is the default one
* returned by the TestWebServer. The header 'Content-Type' is set to 'plain/text'.
*/
@interface Handler200 : TestHandler
{
}
@end /* Handler200 */
/**
* The handler returns the status code 204 for any request.
* The response's content is empty. The status line is 'HTTP/1.1 204 No Content'.
*/
@interface Handler204 : TestHandler
{
}
@end /* Handler204 */
/**
* The handler returns the status code 301 for any request.
* The response's content is an html page like 'Redirect to <a href="URL">'.
* The status line is 'HTTP/1.1 301 Moved permanently'. It also sets
* the response's header 'Location' to the custom URL stored by the
* ivar _URLString. If the -[setURLString:] wasn't called with a proper URL
* string before the first request arrives then the handler will raise
* NSInternalInconsistencyException.
*/
@interface Handler301 : TestHandler
{
NSString *_URLString;
}
- (void)dealloc;
/**
* Sets the URL to which the handler should redirect.
*/
- (void)setURLString:(NSString *)URLString;
@end /* Handler301 */
/**
* The handler returns unconditionally the status code 400 for any request.
* The response's content is 'You have issued a request with invalid data'.
* The status line is 'HTTP/1.1 400 Bad Request'.
*/
@interface Handler400 : TestHandler
{
}
@end /* Handler400 */
/**
* The handler returns unconditionally the status code 401 for any request.
* The response's content is 'Invalid login or password'.
* The status line is 'HTTP/1.1 401 Unauthorized'. It also sets the response's
* header 'WWW-Authenticate' to the value "Basic realm='TestSuit'".
*/
@interface Handler401 : TestHandler
{
}
@end /* Handler401 */
/**
* The handler returns unconditionally the status code 402 for any request.
* The response's content is 'Check your balance'.
* The status line is 'HTTP/1.1 402 Payment required'.
*/
@interface Handler402 : TestHandler
{
}
@end /* Handler402 */
/**
* The handler returns unconditionally the status code 404 for any request.
* The response's content is 'The resource you are trying to access is not found'.
* The status line is 'HTTP/1.1 404 Not found'.
*/
@interface Handler404 : TestHandler
{
}
@end /* Handler404 */
/**
* The handler returns unconditionally the status code 405 for any request.
* The response's content is 'You can't do that'.
* The status line is 'HTTP/1.1 405 Method not allowed'.
*/
@interface Handler405 : TestHandler
{
}
@end /* Handler405 */
/**
* The handler returns unconditionally the status code 409 for any request.
* The response's content is 'The resource you are trying to access is busy'.
* The status line is 'HTTP/1.1 409 Conflict'.
*/
@interface Handler409 : TestHandler
{
}
@end /* Handler409 */
/**
* The handler returns unconditionally the status code 500 for any request.
* The response's content is 'System error'.
* The status line is 'HTTP/1.1 500 Internal Server Error'.
*/
@interface Handler500 : TestHandler
{
}
@end /* Handler500 */
/**
* The handler returns unconditionally the status code 505 for any request.
* The response's content is 'There is network protocol inconsistency'.
* The status line is 'HTTP/1.1 505 Network Protocol Error'.
*/
@interface Handler505 : TestHandler
{
}
@end /* Handler505 */
/**
* The handler returns unconditionally the status code 507 for any request.
* The response's content is 'Insufficient storage'.
* The status line is 'HTTP/1.1 507 Insufficient storage'.
*/
@interface Handler507 : TestHandler
{
}
@end /* Handler507 */
/**
* The handler returns the index page with a list of possible URLs for any request.
* The URLs are constructed from the base URL stored by the ivar _URLString. If
* the -[setURLString:] wasn't called with a proper URL string before the first
* request arrives then the handler will raise NSInternalInconsistencyException.
*/
@interface HandlerIndex : TestHandler
{
NSString *_URLString;
}
- (void)dealloc;
/**
* Sets the URL to which the handler should redirect.
*/
- (void)setURLString:(NSString *)URLString;
@end /* HandlerIndex */
/* The end of the handler collection */

View file

@ -0,0 +1,601 @@
/** -*- objc -*-
*
* Author: Sergei Golovin <Golovin.SV@gmail.com>
*
*/
#import "RequestHandler.h"
#import "TestWebServer.h"
/**
* The abstract class TestHandler. All custom handlers must be derived from it.
*/
@implementation TestHandler
- (id)init
{
if((self = [super init]) != nil)
{
_debug = NO;
_delegate = nil;
_isAuthorized = NO;
}
return self;
}
- (void)dealloc
{
_delegate = nil;
[super dealloc];
}
/* getters */
/* end of getters */
/* setters */
- (void)setDebug:(BOOL)flag
{
_debug = flag;
}
- (void)setDelegate:(id)delegate
{
_delegate = delegate;
}
- (void)setLogin:(NSString *)login
{
ASSIGN(_login, login);
}
- (void)setPassword:(NSString *)password
{
ASSIGN(_password, password);
}
/* end of setters */
/* RequestHandler */
- (BOOL)prehandleRequest:(GSMimeDocument *)request
response:(GSMimeDocument *)response
for:(TestWebServer *)server
{
_isAuthorized = NO; // TODO: move to something like -[reset]
_done = NO;
return YES;
}
- (BOOL) handleRequest: (GSMimeDocument*)request
response: (GSMimeDocument*)response
for: (TestWebServer*)server
{
NSString *auth;
NSString *path;
BOOL ret = NO;
if(YES == _debug)
{
NSLog(@"%@: BEGIN\n%@", self, request);
}
if(nil != _delegate && [_delegate respondsToSelector: @selector(handler:gotRequest:with:)])
{
[_delegate handler: self gotRequest: request with: server];
}
// analyze what the client wants
path = [[request headerNamed:@"x-http-path"] value];
if([path rangeOfString: @"withoutauth"].location == NSNotFound)
{
auth = [[request headerNamed: @"Authorization"] value];
if(!auth) // not authorized
{
[response setHeader: @"http" value: @"HTTP/1.1 401 Unauthorized" parameters: nil];
[response setHeader: @"WWW-Authenticate" value: @"Basic realm='TestSuit'" parameters: nil];
[response setContent: @"Please give login and password"];
if(nil != _delegate && [_delegate respondsToSelector: @selector(handler:willSendUnauthorized:with:)])
{
[_delegate handler: self willSendUnauthorized: response with: server];
}
if(YES == _debug)
{
NSLog(@"%@: about to send Unauthorized\n%@", self, response);
}
return YES;
}
else // the auth header is set
{
NSArray *credentials;
credentials = [auth componentsSeparatedByString: @" "];
if([[credentials objectAtIndex:0] isEqualToString: @"Basic"])
{
credentials = [[GSMimeDocument decodeBase64String: [credentials objectAtIndex:1]]
componentsSeparatedByString: @":"];
if([[credentials objectAtIndex:0] isEqualToString: _login] &&
[[credentials objectAtIndex:1] isEqualToString: _password])
{
if(YES == _debug)
{
NSLog(@"%@: got valid credentials", self);
}
if(nil != _delegate && [_delegate respondsToSelector: @selector(handler:gotAuthorized:with:)])
{
[_delegate handler: self gotAuthorized: request with: server];
}
// sets the flag
_isAuthorized = YES;
}
}
else
{
[response setHeader: @"http" value: @"HTTP/1.1 401 Unauthorized" parameters: nil];
[response setHeader: @"WWW-Authenticate" value: @"Basic realm='TestSuit'" parameters: nil];
[response setContent: @"Try another login or password"];
if(nil != _delegate && [_delegate respondsToSelector: @selector(handler:willSendUnauthorized:with:)])
{
[_delegate handler: self willSendUnauthorized: response with: server];
}
if(YES == _debug)
{
NSLog(@"%@: about to send Unauthorized\n%@", self, response);
}
return YES;
}
}
} // ! withoutauth
else
{
// just sets the flag
_isAuthorized = YES;
}
return ret;
}
- (BOOL)posthandleRequest:(GSMimeDocument *)request
response:(GSMimeDocument *)response
for:(TestWebServer *)server
{
BOOL ret = NO;
if(YES == _done &&
nil != _delegate &&
[_delegate respondsToSelector: @selector(handler:willSend:with:)])
{
[_delegate handler: self willSend: response with: server];
ret = YES;
}
// TODO: may be to move at another place?
if(YES == _debug)
{
NSLog(@"%@: about to send\n%@", self, response);
}
return ret;
}
/* end of RequestHandler */
@end /* TestHandler */
@implementation Handler200
- (BOOL) handleRequest: (GSMimeDocument*)request
response: (GSMimeDocument*)response
for: (TestWebServer*)server
{
BOOL ret = [super handleRequest: request response: response for: server];
if(NO == ret)
{
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
if(isToSetResponse)
{
[response setContent: @"OK"];
}
[response setHeader: @"HTTP" value: @"HTTP/1.1 200 OK" parameters:nil];
[response setHeader: @"content-type" value: @"plain/text" parameters: nil];
_done = YES;
ret = YES;
}
return ret;
}
@end /* Handler200 */
@implementation Handler204
- (BOOL) handleRequest: (GSMimeDocument*)request
response: (GSMimeDocument*)response
for: (TestWebServer*)server
{
BOOL ret = [super handleRequest: request response: response for: server];
if(NO == ret)
{
[response setContent: @""];
[response setHeader: @"HTTP" value: @"HTTP/1.1 204 No Content" parameters:nil];
_done = YES;
ret = YES;
}
return ret;
}
@end /* Handler204 */
@implementation Handler301
- (void)dealloc
{
DESTROY(_URLString);
[super dealloc];
}
- (void)setURLString:(NSString *)URLString
{
ASSIGN(_URLString, URLString);
}
- (BOOL) handleRequest: (GSMimeDocument*)request
response: (GSMimeDocument*)response
for: (TestWebServer*)server
{
BOOL ret = [super handleRequest: request response: response for: server];
if(NO == ret)
{
if(nil != _URLString)
{
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
[response setHeader: @"HTTP" value: @"HTTP/1.1 301 Moved Permanently" parameters: nil];
[response setHeader: @"Location" value: _URLString parameters: nil];
if(isToSetResponse)
{
NSString *method = [[request headerNamed: @"x-http-method"] value];
if(![method isEqualToString: @"HEAD"])
{
NSString *note = [NSString stringWithFormat: @"<html><head></head><body>Redirect to <a href='%@'/></body></html>",
_URLString];
[response setContent: note];
}
}
}
else
{
[NSException raise: NSInternalInconsistencyException
format: @"%@: URL isn't set", self];
}
_done = YES;
ret = YES;
}
return ret;
}
@end /* Handler301 */
@implementation Handler400
- (BOOL) handleRequest: (GSMimeDocument*)request
response: (GSMimeDocument*)response
for: (TestWebServer*)server
{
BOOL ret = [super handleRequest: request response: response for: server];
if(NO == ret)
{
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
if(isToSetResponse)
{
[response setContent: @"You have issued a request with invalid data"];
}
[response setHeader:@"HTTP" value:@"HTTP/1.1 400 Bad Request" parameters:nil];
_done = YES;
ret = YES;
}
return ret;
}
@end /* Handler400 */
@implementation Handler401
- (BOOL) handleRequest: (GSMimeDocument*)request
response: (GSMimeDocument*)response
for: (TestWebServer*)server
{
BOOL ret = [super handleRequest: request response: response for: server];
if(NO == ret)
{
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
[response setHeader:@"HTTP" value: @"HTTP/1.1 401 Unauthorized" parameters:nil];
[response setHeader:@"WWW-Authenticate" value: @"Basic realm='TestSuit'" parameters:nil];
if(isToSetResponse)
{
[response setContent: @"Invalid login or password"];
}
_done = YES;
ret = YES;
}
return ret;
}
@end /* Handler401 */
@implementation Handler402
- (BOOL) handleRequest: (GSMimeDocument*)request
response: (GSMimeDocument*)response
for: (TestWebServer*)server
{
BOOL ret = [super handleRequest: request response: response for: server];
if(NO == ret)
{
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
[response setHeader:@"HTTP" value:@"HTTP/1.1 402 Payment required" parameters:nil];
if(isToSetResponse)
{
[response setContent: @"Check your balance"];
}
_done = YES;
ret = YES;
}
return ret;
}
@end /* Handler402 */
@implementation Handler404
- (BOOL) handleRequest: (GSMimeDocument*)request
response: (GSMimeDocument*)response
for: (TestWebServer*)server
{
BOOL ret = [super handleRequest: request response: response for: server];
if(NO == ret)
{
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
[response setHeader:@"HTTP" value:@"HTTP/1.1 404 Not found" parameters:nil];
if(isToSetResponse)
{
[response setContent: @"The resource you are trying to access is not found"];
}
_done = YES;
ret = YES;
}
return ret;
}
@end /* Handler404 */
@implementation Handler405
- (BOOL) handleRequest: (GSMimeDocument*)request
response: (GSMimeDocument*)response
for: (TestWebServer*)server
{
BOOL ret = [super handleRequest: request response: response for: server];
if(NO == ret)
{
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
[response setHeader:@"HTTP" value:@"HTTP/1.1 405 Method not allowed" parameters:nil];
if(isToSetResponse)
{
[response setContent: @"You can't do that"];
}
_done = YES;
ret = YES;
}
return ret;
}
@end /* Handler405 */
@implementation Handler409
- (BOOL) handleRequest: (GSMimeDocument*)request
response: (GSMimeDocument*)response
for: (TestWebServer*)server
{
BOOL ret = [super handleRequest: request response: response for: server];
if(NO == ret)
{
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
[response setHeader:@"HTTP" value:@"HTTP/1.1 409 Conflict" parameters:nil];
if(isToSetResponse)
{
[response setContent: @"The resource you are trying to access is busy"];
}
_done = YES;
ret = YES;
}
return ret;
}
@end /* Handler409 */
@implementation Handler500
- (BOOL) handleRequest: (GSMimeDocument*)request
response: (GSMimeDocument*)response
for: (TestWebServer*)server
{
BOOL ret = [super handleRequest: request response: response for: server];
if(NO == ret)
{
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
[response setHeader:@"HTTP" value:@"HTTP/1.1 500 Internal Server Error" parameters:nil];
if(isToSetResponse)
{
[response setContent: @"System error"];
}
_done = YES;
ret = YES;
}
return ret;
}
@end /* Handler500 */
@implementation Handler505
- (BOOL) handleRequest: (GSMimeDocument*)request
response: (GSMimeDocument*)response
for: (TestWebServer*)server
{
BOOL ret = [super handleRequest: request response: response for: server];
if(NO == ret)
{
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
[response setHeader:@"HTTP" value:@"HTTP/1.1 505 Network Protocol Error" parameters:nil];
if(isToSetResponse)
{
[response setContent: @"There is network protocol inconsistency"];
}
_done = YES;
ret = YES;
}
return ret;
}
@end /* Handler505 */
@implementation Handler507
- (BOOL) handleRequest: (GSMimeDocument*)request
response: (GSMimeDocument*)response
for: (TestWebServer*)server
{
BOOL ret = [super handleRequest: request response: response for: server];
if(NO == ret)
{
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
[response setHeader:@"HTTP" value: @"HTTP/1.1 507 Insufficient storage" parameters:nil];
if(isToSetResponse)
{
[response setContent: @"Insufficient storage"];
}
_done = YES;
ret = YES;
}
return ret;
}
@end /* Handler507 */
@implementation HandlerIndex
- (void)dealloc
{
DESTROY(_URLString);
[super dealloc];
}
- (void)setURLString:(NSString *)URLString
{
ASSIGN(_URLString, URLString);
}
- (BOOL) handleRequest: (GSMimeDocument*)request
response: (GSMimeDocument*)response
for: (TestWebServer*)server
{
BOOL ret = [super handleRequest: request response: response for: server];
if(NO == ret)
{
if(nil != _URLString)
{
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
if(isToSetResponse)
{
NSString *pattern = @"<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'><head></head><body>\
<a href='%@%@'>This page</a><br> \
<a href='%@%@'>Without authentication</a><br> \
<a href='%@%@'>301</a><br> \
<a href='%@%@'>400</a><br> \
<a href='%@%@'>401</a><br> \
<a href='%@%@'>402</a><br> \
<a href='%@%@'>404</a><br> \
<a href='%@%@'>405</a><br> \
<a href='%@%@'>409</a><br> \
<a href='%@%@'>500</a><br> \
<a href='%@%@'>505</a><br> \
<a href='%@%@'>507</a><br> \
</body></html>";
NSString *index = [NSString stringWithFormat: pattern,
_URLString, @"index",
_URLString, @"withoutauth",
_URLString, @"301",
_URLString, @"400",
_URLString, @"401",
_URLString, @"402",
_URLString, @"404",
_URLString, @"405",
_URLString, @"409",
_URLString, @"500",
_URLString, @"505",
_URLString, @"507"];
[response setContent: index];
[response setHeader: @"content-type" value: @"text/html" parameters: nil];
}
}
else
{
[NSException raise: NSInternalInconsistencyException
format: @"%@: URL isn't set", self];
}
_done = YES;
ret = YES;
}
return ret;
}
@end /* HandlerIndex */

View file

@ -0,0 +1,131 @@
/** -*- objc -*-
*
* Author: Sergei Golovin <Golovin.SV@gmail.com>
*
*/
#import <Foundation/Foundation.h>
#import <GNUstepBase/GSMime.h>
/**
* Implements a simple web server with delegate interaction which mimic of
* the WebServer's delegate protocol.
*
* The SimpleWebServer class currently has many limitations (deficiencies).
* The following is a list of most important ones:
* - supports only one connection simultaneously.
* - doesn't support any transfer-content-encoding (more precisely it uses
* only 'identity' transfer-content-encoding that is without any modification
* of the content/message);
* - the class uses UTF8 by default. It expects a request and produces responses
* in that encoding;
* - it expects an explicit request for closing of the connection (that is
* the request's header 'Connection' must be 'close') or implicitly does it
* if no 'Connection' has been supplied;
* - doesn't support pipelining of requests;
*
* Use the -[setDebug: YES] to raise verbosity.
*/
@interface SimpleWebServer : NSObject
{
/* holds the GSServerStream accepting incoming connections */
GSServerStream *_serverStream;
/* the delegate ... NOT RETAINED...
* see below the protocol SimpleWebServerDelegate */
id _delegate;
/* the debug mode */
BOOL _debug;
/* the address to bind with */
NSString *_address;
/* the port to listen to */
NSString *_port;
/* SSL configuration and options */
NSDictionary *_secure;
/* The following web-server code is derived/stolen from NSURL/Helpers/capture.m */
/* the stream to send */
NSOutputStream *_op;
/* the stream to receive */
NSInputStream *_ip;
/* the collector of received bytes from a client */
NSMutableData *_capture;
/* the number of sent bytes to a client */
unsigned _written;
/* the flag indicating the instance is collecting bytes from a client */
BOOL _readable;
/* the flag indicating the instance is sending bytes to a client */
BOOL _writable;
/* whether to use a secure TLS/SSL connection */
BOOL _isSecure;
/* the request is read */
BOOL _doRespond;
/* the response is written */
BOOL _done;
/* end of the stolen */
/* wether the output stream is ready to write */
BOOL _canRespond;
/* holds the current request */
GSMimeDocument *_request;
/* holds the current response */
GSMimeDocument *_response;
}
- (void)dealloc;
/* getters */
/**
* Returns the string of the port number if the instance is accepting
* connections (is started). Otherwise returns nil.
*/
- (NSString *)port;
/* end of getters */
/* setters */
/**
* Starts the simple web server listening on the supplied address and port.
* The dictionary 'dict' is supplied with additional configuration parameters.
* connections. The dictionary's keys are:
* CertificateFile
* the path to a certificate (if the web server should wait for HTTPS)
* KeyFile
* the path to a key (if the web server should wait for HTTPS)
*/
- (BOOL)setAddress:(NSString *)address
port:(NSString *)port
secure:(NSDictionary *)dict;
/**
* Sets the debug mode.
*/
- (void)setDebug:(BOOL)flag;
/**
* Sets the delegate responding to the selector -[processRequest:response:].
*/
- (void)setDelegate:(id)delegate;
/* end of setters */
/**
* Commands the web server to stop listening.
*/
- (void)stop;
/* The method is derived/stolen from NSURL/Helpers/capture.m */
- (void) stream: (NSStream *)theStream handleEvent: (NSStreamEvent)streamEvent;
@end /* SimpleWebServer */
@protocol SimpleWebServerDelegate
/**
* An implementor gets the supplied request for processing and
* modifies the supplied response which the supplied SimpleWebServer
* server should send back to it's peer if the return value is set
* to YES. Otherwise SimpleWebServer sends the predetermined response
* (TODO).
*/
- (BOOL)processRequest:(GSMimeDocument *)request
response:(GSMimeDocument *)response
for:(SimpleWebServer *)server;
@end /* SimpleWebServerDelegate */

View file

@ -0,0 +1,606 @@
/*
* Author: Sergei Golovin <Golovin.SV@gmail.com>
*/
#import "SimpleWebServer.h"
/* the time step for the runloop */
#define TIMING 0.1
@interface SimpleWebServer (Private)
/**
* Starts a listening (server) stream.
*/
- (void)_openServerStream;
/**
* Stops the listening (server) stream.
*/
- (void)_closeServerStream;
/**
* Reset to prepare for the next request-response cycle.
*/
- (void)_resetCycle;
/**
* Opens the input _ip and output _op streams, schedules them
* on the current runloop. Requires the _ip and _op are not nil.
*/
- (void)_openIOStreams;
/**
* Closes the input _ip and output _op streams, unschedules them
* on the current runloop and clears that ivars.
*/
- (void)_closeIOStreams;
/**
* Tries to recognise if the request's bytes have been read (HTTP message's
* headers and body) and if so then it proceeds the request and produces
* a response. If the response is ready it returns YES.
*/
- (BOOL)_tryCaptured;
@end /* SimpleWebServer (Private) */
@implementation SimpleWebServer
- (void)dealloc
{
_delegate = nil;
DESTROY(_capture);
DESTROY(_request);
DESTROY(_response);
[self _closeIOStreams];
[self _closeServerStream];
DESTROY(_address);
DESTROY(_port);
DESTROY(_secure);
[super dealloc];
}
- (id)init
{
if((self = [super init]) != nil)
{
_debug = NO;
_delegate = nil;
_doRespond = NO;
_done = NO;
_canRespond = NO;
_ip = nil;
_op = nil;
}
return self;
}
/* getters */
- (NSString *)port
{
NSStream *s = _serverStream;
if(nil == s) s = _ip;
if(nil == s) s = _op;
if(nil == s) return nil;
return [s streamStatus] == NSStreamStatusOpen ? _port : nil;
}
/* end of getters */
/* setters */
- (BOOL)setAddress:(NSString *)address
port:(NSString *)port
secure:(NSDictionary *)dict
{
BOOL ret = NO;
if(nil == _serverStream)
{
ASSIGN(_address, address);
ASSIGN(_port, port);
ASSIGN(_secure, dict);
[self _openServerStream];
if(nil != _serverStream)
{
ret = YES;
}
}
else
{
NSLog(@"%@: already started '%@' on '%@'", self, _serverStream, [NSThread currentThread]);
}
return ret;
}
- (void)setDebug:(BOOL)flag
{
_debug = flag;
}
- (void)setDelegate:(id)delegate
{
_delegate = delegate;
}
/* end of setters */
- (void)stop
{
NSRunLoop *rl = [NSRunLoop currentRunLoop];;
if(nil != _serverStream)
{
[self _closeServerStream];
DESTROY(_address);
DESTROY(_port);
DESTROY(_secure);
}
[self _closeIOStreams];
// give a time slice to free all resources
[rl runUntilDate: [NSDate dateWithTimeIntervalSinceNow: TIMING]];
}
/* GSStream's delegate */
/* The method is derived/stolen from NSURL/Helpers/capture.m */
- (void) stream: (NSStream *)theStream handleEvent: (NSStreamEvent)streamEvent
{
NSRunLoop *rl = [NSRunLoop currentRunLoop];
// NSLog(@"Event %p %d", theStream, streamEvent);
switch (streamEvent)
{
case NSStreamEventHasBytesAvailable:
{
if (_ip == nil)
{
[self _resetCycle];
[(GSServerStream*)_serverStream acceptWithInputStream: &_ip
outputStream: &_op];
if (_ip) // it is ok to accept nothing
{
RETAIN(_ip);
RETAIN(_op);
[self _openIOStreams];
[self _closeServerStream];
}
}
if (theStream == _ip)
{
_readable = YES;
while (_readable == YES)
{
unsigned char buffer[BUFSIZ];
int readSize;
readSize = [_ip read: buffer maxLength: sizeof(buffer)];
if (readSize <= 0)
{
_readable = NO;
}
else
{
[_capture appendBytes: buffer length: readSize];
}
}
_doRespond = [self _tryCaptured];
if(_doRespond)
{
// reset the output stream to trigger polling
[_op write: NULL maxLength: 0];
if(YES == _debug)
{
NSLog(@"%@: about to send response\n%@", self, _response);
}
}
}
break;
}
case NSStreamEventHasSpaceAvailable:
{
if(_doRespond && _canRespond)
{
NSMutableData *data;
NSString *status;
NSData *statusData;
char *crlf = "\r\n";
id content;
NSData *contentData;
NSUInteger cLength = 0; // content-length
NSString *connection;
BOOL close = YES;
NSAssert(theStream == _op, @"Wrong stream for writing");
_writable = YES;
// adding the 'Connection' to the response
connection = [[_request headerNamed: @"Connection"] value];
if(nil == connection)
{
connection = [[_request headerNamed: @"connection"] value];
}
// if the client didn't supply the header 'Connection' or explicitly stated
// to close the current connection
close = (nil == connection || [[connection lowercaseString] isEqualToString: @"close"]);
// adding the 'Content-Length' to the response
content = [_response content];
if([content isKindOfClass: [NSString class]])
{
contentData = [(NSString *)content dataUsingEncoding: NSUTF8StringEncoding];
}
else if([content isKindOfClass: [NSData class]])
{
contentData = (NSData *)content;
}
else
{
// yet unsupported
}
if(nil != content)
{
cLength = [contentData length];
if(cLength > 0)
{
[_response setHeader: @"Content-Length"
value: [NSString stringWithFormat: @"%u", cLength]
parameters: nil];
}
}
if(cLength == 0)
{
[_response setHeader: @"Content-Length"
value: @"0"
parameters: nil];
}
// adding the status line
status = [[_response headerNamed: @"http"] value];
if(nil == status)
{
status = [[_response headerNamed: @"HTTP"] value];
}
if(nil == status)
{
status = [[_response headerNamed: @"Http"] value];
}
statusData = [status dataUsingEncoding: NSUTF8StringEncoding];
data = [NSMutableData dataWithData: statusData];
[_response deleteHeaderNamed: @"http"];
[data appendBytes: crlf length: 2];
// actual sending
[data appendData: [_response rawMimeData]];
while (_writable == YES && _written < [data length])
{
int result = [_op write: [data bytes] + _written
maxLength: [data length] - _written];
if (result <= 0)
{
_writable = NO;
}
else
{
_written += result;
}
}
if (_written == [data length])
{
if(close)
{
// if the client didn't supply the header 'Connection' or explicitly stated
// to close the current connection
[self _closeIOStreams];
[self _openServerStream];
}
// ready for another request-response cycle
[self _resetCycle];
}
}
_canRespond = YES;
break;
}
case NSStreamEventEndEncountered:
{
if(theStream == _ip || theStream == _op)
{
[self _closeIOStreams];
[self _resetCycle];
[self _openServerStream];
}
else
{
[theStream close];
[theStream removeFromRunLoop: rl forMode: NSDefaultRunLoopMode];
}
break;
}
case NSStreamEventErrorOccurred:
{
int code = [[theStream streamError] code];
if(theStream == _ip || theStream == _op)
{
[self _closeIOStreams];
[self _resetCycle];
[self _openServerStream];
}
else
{
[theStream close];
[theStream removeFromRunLoop: rl forMode: NSDefaultRunLoopMode];
}
NSAssert1(1, @"Error! code is %d", code);
break;
}
default:
break;
}
}
/* end of GSStream's delegate */
@end /* SimpleWebServer */
@implementation SimpleWebServer (Private)
- (void)_openServerStream
{
NSString *certFile = nil;
NSString *keyFile = nil;
NSRunLoop *rl;
if(nil == _serverStream)
{
rl = [NSRunLoop currentRunLoop];
_serverStream = [GSServerStream serverStreamToAddr: _address port: [_port intValue]];
RETAIN(_serverStream);
if (_serverStream == nil)
{
NSLog(@"Failed to create server stream (address: %@, port: %@)", _address, _port);
return;
}
if(nil != _secure &&
(certFile = [_secure objectForKey: @"CertificateFile"]) != nil &&
(keyFile = [_secure objectForKey: @"KeyFile"]) != nil)
{
_isSecure = YES;
[_serverStream setProperty: NSStreamSocketSecurityLevelTLSv1 forKey: NSStreamSocketSecurityLevelKey];
[_serverStream setProperty: certFile forKey: GSTLSCertificateFile];
[_serverStream setProperty: keyFile forKey: GSTLSCertificateKeyFile];
}
_capture = [NSMutableData new];
[_serverStream setDelegate: self];
[_serverStream scheduleInRunLoop: rl forMode: NSDefaultRunLoopMode];
[_serverStream open];
if(YES == _debug)
{
NSLog(@"%@: started '%@' on '%@'", self, _serverStream, [NSThread currentThread]);
}
}
else
{
NSLog(@"%@: already started '%@' on '%@'", self, _serverStream, [NSThread currentThread]);
}
}
- (void)_closeServerStream
{
NSRunLoop *rl;
if(nil != _serverStream)
{
rl = [NSRunLoop currentRunLoop];
[_serverStream close];
[_serverStream removeFromRunLoop: rl forMode: NSDefaultRunLoopMode];
if(YES == _debug)
{
NSLog(@"%@: stopped server stream %@", self, _serverStream);
}
DESTROY(_serverStream);
}
}
- (void)_resetCycle
{
_written = 0;
_doRespond = NO;
_done = NO;
_canRespond = NO;
[_capture setLength: 0];
}
- (void)_openIOStreams
{
NSRunLoop *rl;
if(_ip != nil && _op != nil)
{
rl = [NSRunLoop currentRunLoop];
[_ip scheduleInRunLoop: rl forMode: NSDefaultRunLoopMode];
[_op scheduleInRunLoop: rl forMode: NSDefaultRunLoopMode];
[_ip setDelegate: self];
[_op setDelegate: self];
[_ip open];
[_op open];
}
else
{
[NSException raise: NSInternalInconsistencyException
format: @"%@: IO streams not properly initialized", self];
}
}
- (void)_closeIOStreams
{
NSRunLoop *rl;
if(nil != _ip || nil != _op)
{
rl = [NSRunLoop currentRunLoop];
[_ip close];
[_ip removeFromRunLoop: rl forMode: NSDefaultRunLoopMode];
[_op close];
[_op removeFromRunLoop: rl forMode: NSDefaultRunLoopMode];
DESTROY(_ip);
DESTROY(_op);
}
}
- (BOOL)_tryCaptured
{
BOOL ret = NO;
NSRange r1;
NSRange r2;
NSString *headers;
NSString *tmp1;
NSString *tmp2;
NSUInteger contentLength;
// the following chunk ensures that the captured data are written only
// when all request's bytes are read... it waits for full headers and
// reads the Content-Length's value then waits for the number of bytes
// equal to that value is read
tmp1 = [[NSString alloc] initWithData: _capture
encoding: NSUTF8StringEncoding];
// whether the headers are read
if((r1 = [tmp1 rangeOfString: @"\r\n\r\n"]).location != NSNotFound)
{
headers = [tmp1 substringToIndex: r1.location + 2];
if((r2 = [[headers lowercaseString] rangeOfString: @"content-length:"]).location != NSNotFound)
{
tmp2 = [headers substringFromIndex: r2.location + r2.length]; // content-length:<tmp2><end of headers>
if((r2 = [tmp2 rangeOfString: @"\r\n"]).location != NSNotFound)
{
// full line with content-length is present
tmp2 = [tmp2 substringToIndex: r2.location]; // number of content's bytes
contentLength = [tmp2 intValue];
}
}
else
{
contentLength = 0; // no header 'content-length'
}
if(r1.location + 4 + contentLength == [_capture length]) // Did we get headers + body?
{
// The request has been received
NSString *method = @"";
NSString *query = @"";
NSString *version = @"";
NSString *scheme = _isSecure ? @"https" : @"http";
NSString *path = @"";
NSData *data;
// TODO: currently no checks
r2 = [headers rangeOfString: @"\r\n"];
while(r2.location == 0)
{
// ignore an empty line before the request line
headers = [headers substringFromIndex: 2];
r2 = [headers rangeOfString: @"\r\n"];
}
// the request line has been caught
tmp2 = [tmp1 substringFromIndex: r2.location + 2]; // whole request without the first line
data = [tmp2 dataUsingEncoding: NSUTF8StringEncoding];
_request = [GSMimeParser documentFromData: data];
RETAIN(_request);
// x-http-...
tmp2 = [headers substringToIndex: r2.location]; // the request line
tmp2 = [tmp2 stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]];
// find the method
r2 = [tmp2 rangeOfString: @" "];
method = [[tmp2 substringToIndex: r2.location] uppercaseString];
tmp2 = [[tmp2 substringFromIndex: r2.location + 1]
stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]];
r2 = [tmp2 rangeOfString: @"?"]; // path?query
if(r2.location != NSNotFound)
{
// path?query
path = [tmp2 substringToIndex: r2.location];
tmp2 = [tmp2 substringFromIndex: r2.location + 1]; // without '?'
r2 = [tmp2 rangeOfString: @" "];
query = [tmp2 substringToIndex: r2.location];
}
else
{
// only path
r2 = [tmp2 rangeOfString: @" "];
path = [tmp2 substringToIndex: r2.location];
}
tmp2 = [[tmp2 substringFromIndex: r2.location + 1]
stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]];
// tmp2 == 'HTTP/<version>'
version = [tmp2 substringFromIndex: 5];
[_request setHeader: @"x-http-method"
value: method
parameters: nil];
[_request setHeader: @"x-http-path"
value: path
parameters: nil];
[_request setHeader: @"x-http-query"
value: query
parameters: nil];
[_request setHeader: @"x-http-scheme"
value: scheme
parameters: nil];
[_request setHeader: @"x-http-version"
value: version
parameters: nil];
if(YES == _debug)
{
NSLog(@"%@: got request\n%@", self, _request);
}
_response = [GSMimeDocument new];
if(nil != _delegate && [_delegate respondsToSelector: @selector(processRequest:response:for:)])
{
ret = [_delegate processRequest: _request response: _response for: self];
}
if(!ret)
{
DESTROY(_response);
_response = [GSMimeDocument new];
[_response setHeader: @"HTTP" value: @"HTTP/1.1 204 No Content" parameters: nil];
[_response setHeader: @"Content-Length" value: @"0" parameters: nil];
ret = YES;
}
}
}
DESTROY(tmp1);
return ret;
}
@end /* SimpleWebServer (Private) */

View file

@ -0,0 +1,129 @@
/* -*- objc -*-
*
* Author: Sergei Golovin <Golovin.SV@gmail.com>
*/
#import <Foundation/Foundation.h>
/* the initial (reset) state of any flag set */
#define NORESULTS 0
/**
* A class representing some test case and implementing
* the protocol TestProgress allows descendants to have two sets
* of flags: the reference one and the actual one.
* The reference flag set is what the class's instance's
* state supposed to be if the test was successfull.
* The actual flag set is the current state of the class's
* instance during execution of the test.
* Both can be used in conjuction to decide about test's
* success or fail by checking at the end of the test
* whether the sets are identical.
*/
@protocol TestProgress
/**
* Resets the actual flags to the pristine state (NORESULTS).
*/
- (void)resetFlags;
/**
* Sets the actual flags defined by the supplied mask.
*/
- (void)setFlags:(NSUInteger)mask;
/**
* Unset the actual flags defined by the supplied mask.
*/
- (void)unsetFlags:(NSUInteger)mask;
/**
* Returns YES if the actual flags defined by the supplied mask are
* set.
*/
- (BOOL)isFlagSet:(NSUInteger)mask;
/**
* Resets the reference flags to the pristine state (NORESULTS).
*/
- (void)resetReferenceFlags;
/**
* Stores the supplied mask as the reference flags state.
* The actual flags state equal to the reference one signifies
* a success.
*/
- (void)setReferenceFlags:(NSUInteger)mask;
/**
* Unset the reference flags defined by the supplied mask.
*/
- (void)unsetReferenceFlags:(NSUInteger)mask;
/**
* Returns YES if the reference flags defined by the supplied mask are
* set.
*/
- (BOOL)isReferenceFlagSet:(NSUInteger)mask;
/**
* Returns YES if all conditions are met (by default if the actual flag set
* is equal to the reference flag set).
*/
- (BOOL)isSuccess;
@end /* TestProgress */
/**
* The abstract class representing a test case and implementing
* the protocol TestProgress.
*/
@interface TestCase : NSObject <TestProgress>
{
/* the flags indicating test progress (the actual one) */
NSUInteger _flags;
/* the reference flag set signifying the test's successful state */
NSUInteger _refFlags;
/* the particular flag indicating a failure not related to the test */
BOOL _failed;
/* the particular flag signifying the test has been done */
BOOL _done;
/* keeps the extra which can likely be of NSDictionary class in which case
* it's keys hold parameters of the test */
id _extra;
/* the debug mode flag */
BOOL _debug;
}
- (id)init;
/**
* A descendant class can place in the own implementation of the method
* some preliminary code needed to conduct the test case. The argument
* 'extra' is for future enhancements. The extra is only retained within
* the method for descendants (the abstarct class doesn't do anything
* with this argument).
* So descendant's implementation MUST call the super's one.
*/
- (void)setUpTest:(id)extra;
/**
* A descendant class should place the code conducting the test in
* the own implementation of the method. The argument 'extra' is for
* future enhancements.
*/
- (void)startTest:(id)extra;
/**
* A descendant class can place in the own implementation of the method
* some cleaning code. The argument 'extra' is for future enhancements.
* Descendant's implementation MUST call the super's one.
*/
- (void)tearDownTest:(id)extra;
/**
* Raises the verbosity level if supplied with YES.
*/
- (void)setDebug:(BOOL)flag;
@end /* TestCase */

View file

@ -0,0 +1,106 @@
/*
* Author: Sergei Golovin <Golovin.SV@gmail.com>
*/
#import "TestCase.h"
@implementation TestCase
- (id)init
{
if((self = [super init]) != nil)
{
[self resetFlags];
[self setReferenceFlags: NORESULTS];
_failed = NO;
_done = NO;
}
return self;
}
- (void)dealloc
{
[self tearDownTest: _extra];
[super dealloc];
}
/* TestProgress */
- (void)resetFlags
{
_flags = NORESULTS;
}
- (void)setFlags:(NSUInteger)mask
{
_flags = _flags | mask;
}
- (void)unsetFlags:(NSUInteger)mask
{
_flags = _flags & ~mask;
}
- (BOOL)isFlagSet:(NSUInteger)mask
{
return ((_flags & mask) == mask);
}
- (void)resetReferenceFlags
{
_refFlags = NORESULTS;
}
- (void)setReferenceFlags:(NSUInteger)mask
{
_refFlags = _refFlags | mask;
}
- (void)unsetReferenceFlags:(NSUInteger)mask
{
_refFlags = _refFlags & ~mask;
}
- (BOOL)isReferenceFlagSet:(NSUInteger)mask
{
return ((_refFlags & mask) == mask);
}
- (BOOL)isSuccess
{
if(!_failed && (_flags == _refFlags))
{
return YES;
}
return NO;
}
/* end of TestProgress */
- (void)setUpTest:(id)extra
{
[self resetFlags];
[self resetReferenceFlags];
_failed = NO;
_done = NO;
ASSIGN(_extra, extra);
}
- (void)startTest:(id)extra
{
// does nothing
}
- (void)tearDownTest:(id)extra
{
DESTROY(_extra);
}
- (void)setDebug:(BOOL)flag
{
_debug = flag;
}
@end /* TestCase */

View file

@ -0,0 +1,274 @@
/** -*- objc -*-
*
* Author: Sergei Golovin <Golovin.SV@gmail.com>
*
* TestWebServer is intended to be used in the tests where a web service is
* needed. The class's instance is instantiated with the method
* -[initWithAddress:port:mode:extra] and then started with the call -[start:].
* When started the TestWebServer starts in it's turn a SimpleWebServer instance
* in the same thread unless it is initialized with the argument 'mode' set to
* 'YES' to run it in a detached thread.
*
* It is designed to call the delegate's callbacks (if implemented) of
* the TestWebServerDelegate protocol for every request made to
* the SimpleWebServer's assigned address on the SimpleWebServer's assigned port
* (by default 127.0.0.1 and 54321 respectively). However it currently doesn't
* handle any request on it's own. Instead the class uses a collection of
* handlers. Every handler makes a custom response. So the TestWebServer only
* has to dispatch a request to a corresponding handler and then to send back
* the handler's response to the SimpleWebServer instance. See RequestHandler
* for more information.
*
* TestWebServer can be supplied with additional parameters via the argument
* 'extra' of the initializer -[initWithAddress:port:mode:extra].
* See that method's description for the currently supported key-value pairs.
*
* The pattern of use:
* ----------------------------------------------------------------------------
* NSDictionary *extra = [NSDictionary dictionaryWithObjectsAndKeys:
* @"https", @"Protocol",
* nil];
* TestWebServer *server = [[TestWebServer alloc] initWithAddress: @"127.0.0.1"
* port: @"54321"
* mode: NO
* extra: extra];
* ADelegateClass *del = [ADelegateClass new];
* [server setDelegate: del];
* [server start: extra];
* ....
* <runloop>
* ....
* [server stop];
* DESTROY(server);
* DESTROY(del);
* ----------------------------------------------------------------------------
*
*
* The TestWebServer can response to the following resource paths:
*
* /index
* displays the page with all supported resources
* /withoutauth/
* if it is a part of the request's URL then the TestWebServer
* will NOT check authentication/authorization of the client.
*
* /204/
* /301/
* /400/
* /401/
* /402/
* /404/
* /405/
* /409/
* /500/
* /505/
* /507/
* a try to access to this resources will lead the TestWebServer
* to produce one of the pre-defined responses with the status code
* is equal to the corresponding number.
* The pre-defined responses are:
* 204 "" (empty string)
* 301 "Redirect to <URL>"
* Returns in the header 'Location' a new <URL> which by default
* has the same protocol, address but the port is incremented by
* 1 (e.g. http://localhost:54322/ with other parameters set to
* their default values).
* 400 "You have issued a request with invalid data"
* 401 "Invalid login or password"
* 403 "Check your balance"
* 404 "The resource you are trying to access is not found"
* 405 "You can't do that"
* 409 "The resource you are trying to access is busy"
* 500 "System error"
* 505 "There is network protocol inconsistency"
* 507 "Insufficient storage"
*
* If you want more verbosity call the method -[setDebug: YES].
*
*/
#import "SimpleWebServer.h"
#import <Foundation/Foundation.h>
@class RequestHandler;
@interface TestWebServer : NSObject <SimpleWebServerDelegate>
{
/* the debug mode flag */
BOOL _debug;
/* the IP address to attach to... see DEFAULTADDRESS at the beginning
* of the implementaion */
NSString *_address;
/* the port to listen on... see DEFAULTPORT in the beginning
* of the implementaion */
NSString *_port;
/* whether the TestWebServer listens for HTTPS requests */
BOOL _isSecure;
/* the login for basic authentication */
NSString *_login;
/* the password for basic authentication */
NSString *_password;
/* holds the extra argument which is for a future use...
* Currently it is expected to be a dictionary with predetermined
* keys ( see the description of -[initWithAddress:port:mode:extra:]) */
id _extra;
/* the flag used as a trigger to stop the detached thread */
BOOL _threadToQuit;
/* holds the SimpleWebServer accepting incoming connections */
SimpleWebServer *_server;
/* holds the detached thread the SimpleWebServer is running on...
* nil if it runs in the same thread as the calling code */
NSThread *_serverThread;
/* the delegate ... NOT RETAINED...
* see below the protocol TestWebServerDelegate */
id _delegate;
/* the lock used for synchronization between threads mainly
* to wait for the SimpleWebServer is started/stopped...
* the condition list:
* READY - the initial condition
* STARTED - the SimpleWebServer has been started;
* STOPPED - the SimpleWebServer has been stopped;
*/
NSConditionLock *_lock;
/* the traversal map used to pick a right handler from the request's path */
NSMutableDictionary *_traversalMap;
}
/**
* Initializes the intance with default parameters.
*/
- (id)init;
/**
* Initializes an instance with it's SimpleWebServer accepting connection on
* the specified address and port. The mode decides whether to run
* the SimpleWebServer on the detached thread (NO means it won't be detached).
* The argument extra is for future enhancement and could be of the class
* NSDictionary whose key-value pairs set various parameters.
* The current code supports the following extra keys:
* 'Login' - the login for basic authentication
* 'Password' - the password for basic authentication
* 'Protocol' - 'http' means waiting for HTTP requests
* 'https' - for HTTPS requests
*/
- (id)initWithAddress:(NSString *)address
port:(NSString *)port
mode:(BOOL)detached
extra:(id)extra;
- (void)dealloc;
/**
* Starts the SimpleWebServer accepting incoming connections.
* The supplied argument is for future enhancement and currently has no meaning.
* It isn't retained by the method.
*/
- (void)start:(id)extra;
/**
* Stops the SimpleWebServer. The instance ceases to accept incoming connections.
*/
- (void)stop;
/* SimpleWebServerDelegate */
/**
* The main job of request-response cycle is done here. The method is called
* when all request's bytes are read. It must be supplied with the incoming
* request and the response document which is about to be sent back to the web
* client. The handling code within the method would change the supplied response.
*/
- (BOOL) processRequest:(GSMimeDocument *)request
response:(GSMimeDocument *)response
for:(SimpleWebServer *)server;
/* end of SimpleWebServerDelegate */
/* getters */
/**
* Returns the address which SimpleWebServer is bound to.
*/
- (NSString *)address;
/**
* Returns the port which the SimpleWebServer listens on.
*/
- (NSString *)port;
/**
* Returns the login for basic authentication.
*/
- (NSString *)login;
/**
* Returns the password for basic authentication.
*/
- (NSString *)password;
/**
* Returns YES if the instance waits for HTTPS requests.
*/
- (BOOL)isSecure;
/* end of getters */
/* setters */
/**
* Sets the delegate implementing TestWebServerDelegate protocol.
* The argument isn't retained by the method.
*/
- (void)setDelegate:(id)delegate;
/**
* Sets the debug mode (more verbose).
*/
- (void)setDebug:(BOOL)mode;
/* end of setters */
@end /* TestWebServer */
/**
* The protocol's methods are called during processing of a request.
*/
@protocol TestWebServerDelegate
/**
* Called by the handler when it has got the supplied request
* from the supplied TestWebServer instance.
*/
- (void)handler:(id)handler
gotRequest:(GSMimeDocument *)request
with:(TestWebServer *)server;
/**
* Called by the handler when it is going to send the response
* on an unauthorized request (an invalid request or no credentials are supplied)
* via the supplied TestWebServer.
*/
- (void)handler:(id)handler
willSendUnauthorized:(GSMimeDocument *)response
with:(TestWebServer *)server;
/**
* Called by the handler when it has got the supplied request
* with valid credentials from the supplied TestWebServer instance.
*/
- (void)handler:(id)handler
gotAuthorized:(GSMimeDocument *)request
with:(TestWebServer *)server;
/**
* Called by the handler when it is going to send the response
* on any request except an unauthorized one via the supplied TestWebServer.
*/
- (void)handler:(id)handler
willSend:(GSMimeDocument *)response
with:(TestWebServer *)server;
/**
* Called when the timeout is exceeded.
*/
- (void)timeoutExceededByHandler:(id)handler;
@end /* TestWebServerDelegate */

View file

@ -0,0 +1,468 @@
/*
* Author: Sergei Golovin <Golovin.SV@gmail.com>
*/
#import "TestWebServer.h"
#import "RequestHandler.h"
@interface TestWebServer (Private)
/**
* Starts the detached thread. It is the entry point of the thread.
*/
- (void)_startDetached:(id)extra;
/**
* Starts the SimpleWebServer.
*/
- (void)_startHTTPServer:(id)extra;
/**
* Stops the SimpleWebServer.
*/
- (void)_stopHTTPServer;
@end
/* default 'constants' */
#define DEFAULTADDRESS @"127.0.0.1"
#define DEFAULTPORT @"54321"
#define DEFAULTMODE NO
#define DEFAULTLOGIN @"login"
#define DEFAULTPASSWORD @"password"
/* lock's conditions */
#define READY 0 /* the initial condition */
#define STARTED 1 /* the SimpleWebServer has been started */
#define STOPPED 2 /* the SimpleWebServer has been stopped */
/* the time step for the runloop */
#define TIMING 0.1
/* the maximum duration of running of the SimpleWebServer instance
after which the server must be shut down */
#define MAXDURATION 3.0
@implementation TestWebServer
- (id)init
{
return [self initWithAddress: DEFAULTADDRESS
port: DEFAULTPORT
mode: DEFAULTMODE
extra: nil];
}
- (id)initWithAddress:(NSString *)address
port:(NSString *)port
mode:(BOOL)detached
extra:(id)extra
{
if((self = [super init]) != nil)
{
_debug = NO;
_address = [[NSHost hostWithName: address] address];
RETAIN(_address);
ASSIGN(_port, port);
ASSIGN(_extra, extra);
_password = nil;
_login = nil;
_isSecure = NO;
if([extra isKindOfClass: [NSDictionary class]])
{
NSDictionary *d = extra;
NSString *proto;
if((_password = [d objectForKey: @"Password"]) != nil)
{
RETAIN(_password);
}
if((_login = [d objectForKey: @"Login"]) != nil)
{
RETAIN(_login);
}
if((proto = [d objectForKey: @"Protocol"]) != nil &&
[[proto lowercaseString] isEqualToString: @"https"])
{
_isSecure = YES;
}
}
if(nil == _login)
{
_login = DEFAULTLOGIN; RETAIN(_login);
}
if(nil == _password)
{
_password = DEFAULTPASSWORD; RETAIN(_password);
}
_traversalMap = [[NSMutableDictionary alloc]
initWithObjectsAndKeys:
[Handler200 class], @"200",
[Handler204 class], @"204",
[Handler301 class], @"301",
[Handler400 class], @"400",
[Handler401 class], @"401",
[Handler402 class], @"402",
[Handler404 class], @"404",
[Handler405 class], @"405",
[Handler409 class], @"409",
[Handler500 class], @"500",
[Handler505 class], @"505",
[Handler507 class], @"507",
[HandlerIndex class], @"index",
nil];
_lock = [[NSConditionLock alloc] initWithCondition: READY];
_threadToQuit = NO;
if(detached)
{
_serverThread = [[NSThread alloc] initWithTarget: self
selector: @selector(_startDetached:)
object: extra];
}
else
{
_serverThread = nil;
}
}
return self;
}
- (void)dealloc
{
DESTROY(_lock);
DESTROY(_address);
DESTROY(_port);
DESTROY(_extra);
DESTROY(_login);
DESTROY(_password);
_delegate = nil;
DESTROY(_traversalMap);
[super dealloc];
}
- (void)start:(id)extra
{
if([_server port] != nil)
{
if(YES == _debug)
{
NSWarnMLog(@"SimpleWebServer already started");
}
return;
}
if(nil != _serverThread)
{
if(![_serverThread isExecuting])
{
NSTimeInterval duration = 0.0;
[_serverThread start];
// wait for the SimpleWebServer is started
while(![_lock tryLockWhenCondition: STARTED] &&
duration < MAXDURATION)
{
[[NSRunLoop currentRunLoop]
runUntilDate: [NSDate dateWithTimeIntervalSinceNow: TIMING]];
duration += TIMING;
}
[_lock unlock];
if(duration >= MAXDURATION &&
nil != _delegate &&
[_delegate respondsToSelector: @selector(timeoutExceededByHandler:)])
{
[_delegate timeoutExceededByHandler: self];
[self stop];
}
}
else
{
NSWarnMLog(@"the detached thread is already started");
}
}
else
{
[self _startHTTPServer: extra];
}
}
- (void)stop
{
if([_server port] == nil)
{
if(YES == _debug)
{
NSWarnMLog(@"SimpleWebServer already stopped");
}
return;
}
if(nil != _serverThread)
{
if([_serverThread isExecuting])
{
NSTimeInterval duration = 0.0;
_threadToQuit = YES; // this makes the detached thread quiting from it's runloop
// wait for the SimpleWebServer is stopped
while(![_lock tryLockWhenCondition: STOPPED] &&
duration < MAXDURATION)
{
[[NSRunLoop currentRunLoop]
runUntilDate: [NSDate dateWithTimeIntervalSinceNow: TIMING]];
duration += TIMING;
}
[_lock unlockWithCondition: READY];
if(duration >= MAXDURATION &&
nil != _delegate &&
[_delegate respondsToSelector: @selector(timeoutExceededByHandler:)])
{
[_delegate timeoutExceededByHandler: self];
}
}
}
else
{
[self _stopHTTPServer];
}
}
- (BOOL) processRequest:(GSMimeDocument *)request
response:(GSMimeDocument *)response
for:(SimpleWebServer *)server
{
NSString *path;
NSString *component;
NSEnumerator *en;
BOOL ret = NO;
id handler;
// analyze the path to infer what the client wants
path = [[request headerNamed:@"x-http-path"] value];
// traverse over the path to search the right handler
en = [[path pathComponents] objectEnumerator];
while((component = [en nextObject]) != nil)
{
if((handler = [_traversalMap objectForKey: component]) != nil)
{
// the handler has been found
break; // while
}
}
if(nil == component)
{
// no component found... default 204
component = @"204";
handler = [_traversalMap objectForKey: component];
}
if(handler == [handler class])
{
// it is a Class not an instance... instantiate and substitute
// instead of the Class
Class cls = (Class)handler;
handler = [[cls alloc] init];
[_traversalMap setObject: handler forKey: component];
}
// set the handler
[handler setDelegate: _delegate];
[handler setLogin: _login];
[handler setPassword: _password];
if([handler respondsToSelector: @selector(setURLString:)])
{
if([handler isKindOfClass: [HandlerIndex class]])
{
NSString *urlString = [NSString stringWithFormat: @"%@://%@:%@/",
_isSecure ? @"https" : @"http",
_address,
_port];
[(HandlerIndex *)handler setURLString: urlString];
}
else if([handler isKindOfClass: [Handler301 class]])
{
// by default http://localhost:54322/
NSString *port = [NSString stringWithFormat: @"%u", [_port intValue] + 1]; // the TestWebServer's port + 1
NSString *urlString = [NSString stringWithFormat: @"%@://%@:%@/",
_isSecure ? @"https" : @"http",
_address,
port];
[(Handler301 *)handler setURLString: urlString];
}
}
// TODO: add session-related conditions here
[handler prehandleRequest: request
response: response
for: self];
// the main job
ret = [handler handleRequest: request
response: response
for: self];
// TODO: analyze 'ret'... if NO a default handler must be called
// TODO: add session-related conditions here
[handler posthandleRequest: request
response: response
for: self];
return ret;
}
/* end of SimpleWebServer delegate */
/* getters */
- (NSString *)address
{
return _address;
}
- (NSString *)port
{
return _port;
}
- (NSString *)login
{
return _login;
}
- (NSString *)password
{
return _password;
}
- (BOOL)isSecure
{
return _isSecure;
}
/* end of getters */
/* setters */
- (void)setDelegate:(id)delegate
{
_delegate = delegate;
}
- (void)setDebug:(BOOL)mode
{
_debug = mode;
}
/* end of setters */
@end /* TestWebServer */
@implementation TestWebServer (Private)
- (void)_startHTTPServer:(id)extra
{
NSString *certPath;
NSString *keyPath;
NSDictionary *secure = nil;
BOOL status;
_server = [SimpleWebServer new];
[_server setDebug: _debug];
[_server setDelegate: self];
if(_isSecure)
{
if([_address isEqualToString: @"127.0.0.1"] ||
[_address isEqualToString: @"localhost"])
{
certPath = [[NSBundle bundleForClass: [self class]]
pathForResource: @"testCert"
ofType: @"pem"];
keyPath = [[NSBundle bundleForClass: [self class]]
pathForResource: @"testKey"
ofType: @"pem"];
secure = [NSDictionary dictionaryWithObjectsAndKeys:
certPath, @"CertificateFile",
keyPath, @"KeyFile",
nil];
}
else
{
// NOTE: generate corresponding certificates for any address differing
// from 127.0.0.1
}
}
status = [_server setAddress: _address port: _port secure: secure]; // run the server
if(!status)
{
[NSException raise: NSInternalInconsistencyException
format: @"The server hasn't run. Perhaps the port %@ is invalid", DEFAULTPORT];
}
}
- (void)_stopHTTPServer
{
if(nil != _server && [_server port] != nil)
{
[_server stop]; // shut down the server
if(YES == _debug)
{
NSLog(@"%@: stopped SimpleWebServer %@", self, _server);
}
DESTROY(_server);
}
}
- (void)_startDetached:(id)extra
{
CREATE_AUTORELEASE_POOL(arp);
NSTimeInterval duration = 0.0;
[_lock lockWhenCondition: READY];
[self _startHTTPServer: extra];
[_lock unlockWithCondition: STARTED];
if(YES == _debug)
{
NSLog(@"%@: enter into runloop in detached thread %@", self, [NSThread currentThread]);
}
while(!_threadToQuit && duration < MAXDURATION)
{
[[NSRunLoop currentRunLoop]
runUntilDate: [NSDate dateWithTimeIntervalSinceNow: TIMING]];
duration += TIMING;
}
if(YES == _debug)
{
NSLog(@"%@: exit from runloop in detached thread %@", self, [NSThread currentThread]);
}
if(duration >= MAXDURATION &&
nil != _delegate &&
[_delegate respondsToSelector: @selector(timeoutExceededByHandler:)])
{
[_delegate timeoutExceededByHandler: self];
}
[_lock lockWhenCondition: STARTED];
[self _stopHTTPServer];
[_lock unlockWithCondition: STOPPED];
DESTROY(arp);
}
@end /* TestWebServer (Private) */

View file

@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIID6TCCAtGgAwIBAgIJAP02s2/x3i8ZMA0GCSqGSIb3DQEBCwUAMIGKMQswCQYD
VQQGEwJYWDEOMAwGA1UECAwFV29ybGQxEDAOBgNVBAcMB0dOVXN0ZXAxITAfBgNV
BAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0
MSIwIAYJKoZIhvcNAQkBFhNnbnVzdGVwLWRldkBnbnUub3JnMB4XDTE0MDgwNzEx
MTIxN1oXDTI0MDgwNDExMTIxN1owgYoxCzAJBgNVBAYTAlhYMQ4wDAYDVQQIDAVX
b3JsZDEQMA4GA1UEBwwHR05Vc3RlcDEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMRIwEAYDVQQDDAlsb2NhbGhvc3QxIjAgBgkqhkiG9w0BCQEWE2du
dXN0ZXAtZGV2QGdudS5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQDxFVEHh137hyl0juYvbXuAOUIXRSVwk92mAGJIIn0g0Dm6KIAJW1EGR5LeHY3L
vrkxEAvGxb7Ypqtg1F4OcwoZE/1Y0xKjHBnqtMJcw7DgN9F1dIkQ9HxHkYPiHzTF
d7floomsjyt0BcqAqE1Qf0ahsnveq9E6KTIYRTZ91RGHQrAW4KBxFM30ieHYQYdn
2vDgH/8wyLjwQ+89P5SrBJdt+eKHPjHvjs1WZe0i660hvLa19l/DQ5ZxlV5LJ6tZ
yjvr+OjT9tE86r9n34vk+ZZBCQwvzZ+dHwLpufz3VgPap54bCCLY21BZj15nqITN
k+8XJz+aavFe3zdMvT9cGiZNAgMBAAGjUDBOMB0GA1UdDgQWBBRB6fstnCsIvg3r
9f3EmiZhLS867TAfBgNVHSMEGDAWgBRB6fstnCsIvg3r9f3EmiZhLS867TAMBgNV
HRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQC0fy/lWgBZRTXfAUOfZp5sWJcQ
mQKQiPfXYxiUALq6iI1SyQq90kJ1DyLEGJJ9HEaP3s3jyFQgLXoi1J/8qyOESUDy
ogC8nIod6vfA9g8eWcFeOEd6YnNWykPjGCqA/mzrzN3abFkERap8ivx4RWVYX9bP
ZNhJVxcoqVCjtFmIh6ATNG/0+xfxax4U4GitcHNYD0Ij+qdXqNJHym67uVponLYE
SnxxF3lZ6FzB51SyKtJF5j4/fEeyDXR8Uy3zJdrb6mhSUJNX5RYcrdiR9RflrfGy
qYOXFtWmFv/+w/OaqugXQfx9By8uI49U9BG8Q7xSsZSXmoEuMGYt+UwE7UP+
-----END CERTIFICATE-----

View file

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA8RVRB4dd+4cpdI7mL217gDlCF0UlcJPdpgBiSCJ9INA5uiiA
CVtRBkeS3h2Ny765MRALxsW+2KarYNReDnMKGRP9WNMSoxwZ6rTCXMOw4DfRdXSJ
EPR8R5GD4h80xXe35aKJrI8rdAXKgKhNUH9GobJ73qvROikyGEU2fdURh0KwFuCg
cRTN9Inh2EGHZ9rw4B//MMi48EPvPT+UqwSXbfnihz4x747NVmXtIuutIby2tfZf
w0OWcZVeSyerWco76/jo0/bRPOq/Z9+L5PmWQQkML82fnR8C6bn891YD2qeeGwgi
2NtQWY9eZ6iEzZPvFyc/mmrxXt83TL0/XBomTQIDAQABAoIBAAZmlnwor+oZsJQT
pzDjK0BAROzxPQk8I8pggDuCDuhsHtw+bwfQkNol1FRpXHZoXepbjrR8U5DU+//a
I5UmoMIBsdxF3lzORjHhErf7yhpp4PnJWkpE83fC+Ulroq8LeqpyIk2ej3zJGpNH
5KWae3mXj4pd7XQp29ahH80/dvOsWGZyYOXqh7jO6UdXfhPHKIhLN6wd9nzmRy44
PLFcQYo/nTM19ackz9joovyGPy87BGsj3+rALRXd+GMPVtRFFPMtm6PnSTsMsoDg
HEsm76CygnmL7/ywIXSVY/rfomm73SV9FgFalkUSpNWdLqiXiLICVTKC2P1ZUCdg
eL/A59ECgYEA/nfw/9Vh86LVQ6Vqfp9W/pAut9ik8SecqodOptBSVV2YBYcfBejL
dqksW3LaARabGMJh1z5tHfIw+buF7tl8OIy+TNYRNdrUlsNpI7lWVCVp5F4Znr3c
a7GsGE82/XA3eiEZhUfaQeCctOMXoPnZO32mdDal3rYYQ7yKMvAO8gsCgYEA8ojA
pK80FURe+kM0Zd/ftTAzMkGJNWnKkSSqd+sI7iCwzd9KAX3d+EM4/vqguMXzw+MP
FMkXOv4PUP0iV1XyBt1hn+OSdfax4mD9ALOyJoCHvLPsRjlIv7iEsD2oi84mJ07v
3fFuku+xDrPwMBJgeIWjC2ml9J3YYrB5ppPRmAcCgYEA+64/Y5l1ttW/XpeVm8UW
8tJCEr2obYfDMPqAtQZn2FyohhcdfOfBjQxHfe87ZUYpgjSHNq9clvi6rdVl41Wh
wgCaGz7CaOSVzMNbEuU1WCZk9GSJrHKWNsHUt3pppgK+LAHezu7BFNUFyPauoR1c
WLWu01RVe8/Yce5hNX4vGf8CgYEA5w7pmPthfzFX2szTyopyMcftvl85PK3A0m5A
CWbdZx+10Sx88Nbc9Xv1fNWA8QeFqIVVBNRfUVBhfyLp6JJ0tZ2LOCwyiDeyWJ1V
66lGe+/PYTN4UZ6ZdC1yHAVh4W9QYfqOAr/UPCAman96wBGB3tBR+Ll55YXLdJn0
C4KgF1kCgYAHjE0j82DF+6sKw+ggUcMgLM0z6HcYE975T/c4KNqb11Mu2+38CQvY
pvlzOgQxzbjq59reek3gRtj3u0UCaXzTnIvgLl+sC0/l4f4qEVwcEcnMiJyUAIsR
VR7Mw874clbF8TK8tLlcgCipmDmWxFlrgxOPs1ikcObvTOR/oxacUQ==
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,101 @@
/**
*
* Author: Sergei Golovin <Golovin.SV@gmail.com>
*
* Runs two TestWebServer instances to check how the class TestWebServer
* behaves. Visit http://localhost:54321/index to see all supported resources.
*
* If you visit the main TestWebServer instance with the following command:
*
* wget -O - --user=login --password=password http://localhost:54321/301 2>&1
*
* you should get a session log like this:
*
* --2014-08-13 12:08:01-- http://localhost:54321/301
* Resolving localhost (localhost)... 127.0.0.1
* Connecting to localhost (localhost)|127.0.0.1|:54321... connected.
* HTTP request sent, awaiting response... 401 Unauthorized
* Reusing existing connection to localhost:54321.
* HTTP request sent, awaiting response... 301 Moved Permanently
* Location: http://127.0.0.1:54322/ [following]
* --2014-08-13 12:08:01-- http://127.0.0.1:54322/
* Connecting to 127.0.0.1:54322... connected.
* HTTP request sent, awaiting response... 401 Unauthorized
* Reusing existing connection to 127.0.0.1:54322.
* HTTP request sent, awaiting response... 204 No Content
* Length: 0
* Saving to: STDOUT
*
* 0K 0.00 =0s
*
* 2014-08-13 12:08:01 (0.00 B/s) - written to stdout [0/0]
*
*/
#import <Foundation/Foundation.h>
#import "TestWebServer.h"
#import "NSURLConnectionTest.h"
#define TIMING 0.1
int main(int argc, char **argv, char **env)
{
CREATE_AUTORELEASE_POOL(arp);
NSFileManager *fm;
NSBundle *bundle;
BOOL loaded;
NSString *helperPath;
fm = [NSFileManager defaultManager];
helperPath = [[fm currentDirectoryPath]
stringByAppendingString: @"/TestConnection.bundle"];
bundle = [NSBundle bundleWithPath: helperPath];
loaded = [bundle load];
if(loaded)
{
TestWebServer *server1;
TestWebServer *server2;
Class testClass;
BOOL debug = YES;
NSDictionary *d;
testClass = [bundle principalClass]; // NSURLConnectionTest
d = [NSDictionary dictionaryWithObjectsAndKeys:
// @"https", @"Protocol",
nil];
server1 = [[[testClass testWebServerClass] alloc] initWithAddress: @"localhost"
port: @"54321"
mode: NO
extra: d];
[server1 setDebug: debug];
[server1 start: d]; // 127.0.0.1:54321 HTTP
server2 = [[[testClass testWebServerClass] alloc] initWithAddress: @"localhost"
port: @"54322"
mode: NO
extra: d];
[server2 setDebug: debug];
[server2 start: d]; // 127.0.0.1:54322 HTTP
while(YES)
{
[[NSRunLoop currentRunLoop]
runUntilDate: [NSDate dateWithTimeIntervalSinceNow: TIMING]];
}
// [server1 stop];
// DESTROY(server1);
// [server2 stop];
// DESTROY(server2);
}
else
{
[NSException raise: NSInternalInconsistencyException
format: @"can't load bundle TestConnection"];
}
DESTROY(arp);
return 0;
}

View file

@ -0,0 +1,137 @@
/**
* Tests for HTTP.
*/
#import <Foundation/Foundation.h>
#import "Helpers/NSURLConnectionTest.h"
#import "Helpers/TestWebServer.h"
#import <Testing.h>
int main(int argc, char **argv, char **env)
{
CREATE_AUTORELEASE_POOL(arp);
NSFileManager *fm;
NSBundle *bundle;
BOOL loaded;
NSString *helperPath;
// load the test suite's classes
fm = [NSFileManager defaultManager];
helperPath = [[fm currentDirectoryPath]
stringByAppendingString: @"/Helpers/TestConnection.bundle"];
bundle = [NSBundle bundleWithPath: helperPath];
loaded = [bundle load];
if(loaded)
{
NSDictionary *d;
Class testClass;
NSDictionary *refs;
TestWebServer *server;
NSURLConnectionTest *testCase;
BOOL debug = NO;
testClass = [bundle principalClass]; // NSURLConnectionTest
// create a shared TestWebServer instance for performance
server = [[testClass testWebServerClass] new];
[server setDebug: debug];
[server start: nil]; // 127.0.0.1:54321 HTTP
/*
* Simple GET via HTTP with empty response's body and
* the response's status code 204 (by default)
*/
testCase = [testClass new];
[testCase setDebug: debug];
// the extra dictionary with test case's parameters
d = [NSDictionary dictionaryWithObjectsAndKeys:
server, @"Instance", // we use the shared TestWebServer instance
nil];
[testCase setUpTest: d];
[testCase startTest: d];
PASS([testCase isSuccess], "GET http://localhost:54321");
[testCase tearDownTest: d];
DESTROY(testCase);
/*
* Simple GET via HTTP with the response's status code 400 and
* non-empty response's body
*/
testCase = [testClass new];
[testCase setDebug: debug];
// the extra dictionary with test case's parameters
d = [NSDictionary dictionaryWithObjectsAndKeys:
server, @"Instance", // we use the shared TestWebServer instance
@"400", @"Path", // request the handler responding with 400
@"400", @"StatusCode", // the expected status code
@"You have issued a request with invalid data", @"Content", // the expected response's body
nil];
[testCase setUpTest: d];
[testCase startTest: d];
PASS([testCase isSuccess], "response 400 .... GET http://localhost:54321/400");
[testCase tearDownTest: d];
DESTROY(testCase);
/*
* Simple POST via HTTP with the response's status code 400 and
* non-empty response's body
*/
testCase = [testClass new];
[testCase setDebug: debug];
// the extra dictionary with test case's parameters
d = [NSDictionary dictionaryWithObjectsAndKeys:
server, @"Instance", // we use the shared TestWebServer instance
@"400", @"Path", // request the handler responding with 400
@"400", @"StatusCode", // the expected status code
@"You have issued a request with invalid data", @"Content", // the expected response's body
@"Some payload", @"Payload", // the custom payload
@"POST", @"Method", // use POST
nil];
[testCase setUpTest: d];
[testCase startTest: d];
PASS([testCase isSuccess], "payload... response 400 .... POST http://localhost:54321/400");
[testCase tearDownTest: d];
DESTROY(testCase);
/*
* Tests redirecting... it uses an auxilliary TestWebServer instance and proceeds
* in two stages. The first one is to get the status code 301 and go to the URL
* given in the response's header 'Location'. The second stage is a simple GET on
* the given URL with the status code 204 and empty response's body.
*/
testCase = [testClass new];
[testCase setDebug: debug];
// the reference set difference (from the default reference set) we expect
refs = [NSDictionary dictionaryWithObjectsAndKeys:
@"YES", @"GOTREDIRECT",
nil];
// the extra dictionary with test case's parameters
d = [NSDictionary dictionaryWithObjectsAndKeys:
server, @"Instance", // we use the shared TestWebServer instance
@"/301", @"Path", // request the handler responding with a redirect
@"/", @"RedirectPath", // the URL's path of redirecting
@"YES", @"IsAuxilliary", // start an auxilliary TestWebServer instance
refs, @"ReferenceFlags", // the expected reference set difference
nil];
[testCase setUpTest: d];
[testCase startTest: d];
PASS([testCase isSuccess], "redirecting... GET http://localhost:54321/301");
[testCase tearDownTest: d];
DESTROY(testCase);
// cleaning
[server stop];
DESTROY(server);
}
else
{
// no classes no tests
[NSException raise: NSInternalInconsistencyException
format: @"can't load bundle TestConnection"];
}
DESTROY(arp);
return 0;
}

View file

@ -0,0 +1,145 @@
/**
* Tests for HTTPS
* (must be the same as test02.m except for the use of HTTPS).
*/
#import <Foundation/Foundation.h>
#import "Helpers/NSURLConnectionTest.h"
#import "Helpers/TestWebServer.h"
#import <Testing.h>
int main(int argc, char **argv, char **env)
{
CREATE_AUTORELEASE_POOL(arp);
NSFileManager *fm;
NSBundle *bundle;
BOOL loaded;
NSString *helperPath;
// load the test suite's classes
fm = [NSFileManager defaultManager];
helperPath = [[fm currentDirectoryPath]
stringByAppendingString: @"/Helpers/TestConnection.bundle"];
bundle = [NSBundle bundleWithPath: helperPath];
loaded = [bundle load];
if(loaded)
{
NSDictionary *d;
Class testClass;
NSDictionary *refs;
TestWebServer *server;
NSURLConnectionTest *testCase;
BOOL debug = NO;
testClass = [bundle principalClass]; // NSURLConnectionTest
// the extra dictionary commanding to use HTTPS
d = [NSDictionary dictionaryWithObjectsAndKeys:
@"HTTPS", @"Protocol",
nil];
// create a shared TestWebServer instance for performance
server = [[[testClass testWebServerClass] alloc] initWithAddress: @"localhost"
port: @"54321"
mode: NO
extra: d];
[server setDebug: debug];
[server start: d]; // 127.0.0.1:54321 HTTPS
/*
* Simple GET via HTTPS with empty response's body and
* the response's status code 204 (by default)
*/
testCase = [testClass new];
[testCase setDebug: debug];
// the extra dictionary with test case's parameters
d = [NSDictionary dictionaryWithObjectsAndKeys:
server, @"Instance", // we use the shared TestWebServer instance
nil];
[testCase setUpTest: d];
[testCase startTest: d];
PASS([testCase isSuccess], "HTTPS... GET https://localhost:54321");
[testCase tearDownTest: d];
DESTROY(testCase);
/*
* Simple GET via HTTPS with the response's status code 400 and
* non-empty response's body
*/
testCase = [testClass new];
[testCase setDebug: debug];
// the extra dictionary with test case's parameters
d = [NSDictionary dictionaryWithObjectsAndKeys:
server, @"Instance", // we use the shared TestWebServer instance
@"400", @"Path", // request the handler responding with 400
@"400", @"StatusCode", // the expected status code
@"You have issued a request with invalid data", @"Content", // the expected response's body
nil];
[testCase setUpTest: d];
[testCase startTest: d];
PASS([testCase isSuccess], "HTTPS... response 400 .... GET https://localhost:54321/400");
[testCase tearDownTest: d];
DESTROY(testCase);
/*
* Simple POST via HTTPS with the response's status code 400 and
* non-empty response's body
*/
testCase = [testClass new];
[testCase setDebug: debug];
// the extra dictionary with test case's parameters
d = [NSDictionary dictionaryWithObjectsAndKeys:
server, @"Instance", // we use the shared TestWebServer instance
@"400", @"Path", // request the handler responding with 400
@"400", @"StatusCode", // the expected status code
@"You have issued a request with invalid data", @"Content", // the expected response's body
@"Some payload", @"Payload", // the custom payload
@"POST", @"Method", // use POST
nil];
[testCase setUpTest: d];
[testCase startTest: d];
PASS([testCase isSuccess], "HTTPS... payload... response 400 .... POST https://localhost:54321/400");
[testCase tearDownTest: d];
DESTROY(testCase);
/*
* Tests redirecting... it uses an auxilliary TestWebServer instance and proceeds
* in two stages. The first one is to get the status code 301 and go to the URL
* given in the response's header 'Location'. The second stage is a simple GET on
* the given URL with the status code 204 and empty response's body.
*/
testCase = [testClass new];
[testCase setDebug: debug];
// the reference set difference (from the default reference set) we expect
refs = [NSDictionary dictionaryWithObjectsAndKeys:
@"YES", @"GOTREDIRECT",
nil];
// the extra dictionary with test case's parameters
d = [NSDictionary dictionaryWithObjectsAndKeys:
server, @"Instance", // we use the shared TestWebServer instance
@"/301", @"Path", // request the handler responding with a redirect
@"/", @"RedirectPath", // the URL's path of redirecting
@"YES", @"IsAuxilliary", // start an auxilliary TestWebServer instance
refs, @"ReferenceFlags", // the expected reference set difference
nil];
[testCase setUpTest: d];
[testCase startTest: d];
PASS([testCase isSuccess], "HTTPS... redirecting... GET https://localhost:54321/301");
[testCase tearDownTest: d];
DESTROY(testCase);
// cleaning
[server stop];
DESTROY(server);
}
else
{
// no classes no tests
[NSException raise: NSInternalInconsistencyException
format: @"can't load bundle TestConnection"];
}
DESTROY(arp);
return 0;
}

View file

@ -0,0 +1,170 @@
/**
* Tests for HTTP without authorization.
*/
#import <Foundation/Foundation.h>
#import "Helpers/NSURLConnectionTest.h"
#import "Helpers/TestWebServer.h"
#import <Testing.h>
int main(int argc, char **argv, char **env)
{
CREATE_AUTORELEASE_POOL(arp);
NSFileManager *fm;
NSBundle *bundle;
BOOL loaded;
NSString *helperPath;
// load the test suite's classes
fm = [NSFileManager defaultManager];
helperPath = [[fm currentDirectoryPath]
stringByAppendingString: @"/Helpers/TestConnection.bundle"];
bundle = [NSBundle bundleWithPath: helperPath];
loaded = [bundle load];
if(loaded)
{
NSDictionary *d;
Class testClass;
NSDictionary *refs;
TestWebServer *server;
NSURLConnectionTest *testCase;
BOOL debug = NO;
testClass = [bundle principalClass]; // NSURLConnectionTest
// create a shared TestWebServer instance for performance
server = [[testClass testWebServerClass] new];
[server setDebug: debug];
[server start: nil]; // 127.0.0.1:54321 HTTP
/*
* Simple GET via HTTP without authorization with empty response's body and
* the response's status code 204 (by default)
*/
testCase = [testClass new];
[testCase setDebug: debug];
// the reference set difference (from the default reference set) we expect
// the flags must not be set because we request a path with no authorization
// See NSURLConnectionTest.h for details (the key words are 'TestCase' and 'ReferenceFlags')
refs = [NSDictionary dictionaryWithObjectsAndKeys:
@"NO", @"GOTUNAUTHORIZED",
@"NO", @"AUTHORIZED",
@"NO", @"NOTAUTHORIZED",
nil];
// the extra dictionary with test case's parameters
d = [NSDictionary dictionaryWithObjectsAndKeys:
server, @"Instance", // we use the shared TestWebServer instance
@"/withoutauth", @"Path", // the path commands to use no authorization
refs, @"ReferenceFlags", // the expected reference set difference
nil];
[testCase setUpTest: d];
[testCase startTest: d];
PASS([testCase isSuccess], "no auth... GET http://localhost:54321/withoutauth");
[testCase tearDownTest: d];
DESTROY(testCase);
/*
* Simple GET via HTTP without authorization with the response's status code 400
* and non-empty response's body
*/
testCase = [testClass new];
[testCase setDebug: debug];
// the reference set difference (from the default reference set) we expect
// the flags must not be set because we request a path with no authorization
// See NSURLConnectionTest.h for details (the key words are 'TestCase' and 'ReferenceFlags')
refs = [NSDictionary dictionaryWithObjectsAndKeys:
@"NO", @"GOTUNAUTHORIZED",
@"NO", @"AUTHORIZED",
@"NO", @"NOTAUTHORIZED",
nil];
// the extra dictionary with test case's parameters
d = [NSDictionary dictionaryWithObjectsAndKeys:
server, @"Instance", // we use the shared TestWebServer instance
@"400/withoutauth", @"Path", // request the handler responding with 400
@"400", @"StatusCode", // the expected status code
@"You have issued a request with invalid data", @"Content", // the expected response's body
refs, @"ReferenceFlags", // the expected reference set difference
nil];
[testCase setUpTest: d];
[testCase startTest: d];
PASS([testCase isSuccess], "no auth... response 400 .... GET http://localhost:54321/400/withoutauth");
[testCase tearDownTest: d];
DESTROY(testCase);
/*
* Simple POST via HTTP with the response's status code 400 and
* non-empty response's body
*/
testCase = [testClass new];
[testCase setDebug: debug];
// the reference set difference (from the default reference set) we expect
// the flags must not be set because we request a path with no authorization
// See NSURLConnectionTest.h for details (the key words are 'TestCase' and 'ReferenceFlags')
refs = [NSDictionary dictionaryWithObjectsAndKeys:
@"NO", @"GOTUNAUTHORIZED",
@"NO", @"AUTHORIZED",
@"NO", @"NOTAUTHORIZED",
nil];
// the extra dictionary with test case's parameters
d = [NSDictionary dictionaryWithObjectsAndKeys:
server, @"Instance", // we use the shared TestWebServer instance
@"400/withoutauth", @"Path", // request the handler responding with 400
@"400", @"StatusCode", // the expected status code
@"You have issued a request with invalid data", @"Content", // the expected response's body
@"Some payload", @"Payload", // the custom payload
@"POST", @"Method", // use POST
refs, @"ReferenceFlags", // the expected reference set difference
nil];
[testCase setUpTest: d];
[testCase startTest: d];
PASS([testCase isSuccess], "no auth... payload... response 400 .... POST http://localhost:54321/400/withoutauth");
[testCase tearDownTest: d];
DESTROY(testCase);
/*
* Tests redirecting... it uses an auxilliary TestWebServer instance and proceeds
* in two stages. The first one is to get the status code 301 and go to the URL
* given in the response's header 'Location'. The second stage is a simple GET on
* the given URL with the status code 204 and empty response's body.
*/
testCase = [testClass new];
[testCase setDebug: debug];
// the reference set difference (from the default reference set) we expect
// the flags must not be set because we request a path with no authorization
// See NSURLConnectionTest.h for details (the key words are 'TestCase' and 'ReferenceFlags')
refs = [NSDictionary dictionaryWithObjectsAndKeys:
@"NO", @"GOTUNAUTHORIZED",
@"NO", @"AUTHORIZED",
@"NO", @"NOTAUTHORIZED",
@"YES", @"GOTREDIRECT",
nil];
// the extra dictionary with test case's parameters
d = [NSDictionary dictionaryWithObjectsAndKeys:
server, @"Instance", // we use the shared TestWebServer instance
@"/301/withoutauth", @"Path", // request the handler responding with a redirect
@"/withoutauth", @"RedirectPath", // the URL's path of redirecting
@"YES", @"IsAuxilliary", // start an auxilliary TestWebServer instance
refs, @"ReferenceFlags", // the expected reference set difference
nil];
[testCase setUpTest: d];
[testCase startTest: d];
PASS([testCase isSuccess], "no auth... redirecting... GET http://localhost:54321/301/withoutauth");
[testCase tearDownTest: d];
DESTROY(testCase);
// cleaning
[server stop];
DESTROY(server);
}
else
{
// no classes no tests
[NSException raise: NSInternalInconsistencyException
format: @"can't load bundle TestConnection"];
}
DESTROY(arp);
return 0;
}

View file

@ -0,0 +1,178 @@
/**
* Tests for HTTPS without authorization.
* (must be the same as test04.m except for the use of HTTPS).
*/
#import <Foundation/Foundation.h>
#import "Helpers/NSURLConnectionTest.h"
#import "Helpers/TestWebServer.h"
#import <Testing.h>
int main(int argc, char **argv, char **env)
{
CREATE_AUTORELEASE_POOL(arp);
NSFileManager *fm;
NSBundle *bundle;
BOOL loaded;
NSString *helperPath;
// load the test suite's classes
fm = [NSFileManager defaultManager];
helperPath = [[fm currentDirectoryPath]
stringByAppendingString: @"/Helpers/TestConnection.bundle"];
bundle = [NSBundle bundleWithPath: helperPath];
loaded = [bundle load];
if(loaded)
{
NSDictionary *d;
Class testClass;
NSDictionary *refs;
TestWebServer *server;
NSURLConnectionTest *testCase;
BOOL debug = NO;
testClass = [bundle principalClass]; // NSURLConnectionTest
// the extra dictionary commanding to use HTTPS
d = [NSDictionary dictionaryWithObjectsAndKeys:
@"HTTPS", @"Protocol",
nil];
// create a shared TestWebServer instance for performance
server = [[[testClass testWebServerClass] alloc] initWithAddress: @"localhost"
port: @"54321"
mode: NO
extra: d];
[server setDebug: debug];
[server start: d]; // 127.0.0.1:54321 HTTPS
/*
* Simple GET via HTTPS without authorization with empty response's body and
* the response's status code 204 (by default)
*/
testCase = [testClass new];
[testCase setDebug: debug];
// the reference set difference (from the default reference set) we expect
// the flags must not be set because we request a path with no authorization
// See NSURLConnectionTest.h for details (the key words are 'TestCase' and 'ReferenceFlags')
refs = [NSDictionary dictionaryWithObjectsAndKeys:
@"NO", @"GOTUNAUTHORIZED",
@"NO", @"AUTHORIZED",
@"NO", @"NOTAUTHORIZED",
nil];
// the extra dictionary with test case's parameters
d = [NSDictionary dictionaryWithObjectsAndKeys:
server, @"Instance", // we use the shared TestWebServer instance
@"/withoutauth", @"Path", // the path commands to use no authorization
refs, @"ReferenceFlags", // the expected reference set difference
nil];
[testCase setUpTest: d];
[testCase startTest: d];
PASS([testCase isSuccess], "HTTPS... no auth...GET https://localhost:54321/withoutauth");
[testCase tearDownTest: d];
DESTROY(testCase);
/*
* Simple GET via HTTPS without authorization with the response's status code 400
* and non-empty response's body
*/
testCase = [testClass new];
[testCase setDebug: debug];
// the reference set difference (from the default reference set) we expect
// the flags must not be set because we request a path with no authorization
// See NSURLConnectionTest.h for details (the key words are 'TestCase' and 'ReferenceFlags')
refs = [NSDictionary dictionaryWithObjectsAndKeys:
@"NO", @"GOTUNAUTHORIZED",
@"NO", @"AUTHORIZED",
@"NO", @"NOTAUTHORIZED",
nil];
// the extra dictionary with test case's parameters
d = [NSDictionary dictionaryWithObjectsAndKeys:
server, @"Instance", // we use the shared TestWebServer instance
@"400/withoutauth", @"Path", // request the handler responding with 400
@"400", @"StatusCode", // the expected status code
@"You have issued a request with invalid data", @"Content", // the expected response's body
refs, @"ReferenceFlags", // the expected reference set difference
nil];
[testCase setUpTest: d];
[testCase startTest: d];
PASS([testCase isSuccess], "HTTPS... no auth... response 400... GET https://localhost:54321/400/withoutauth");
[testCase tearDownTest: d];
DESTROY(testCase);
/*
* Simple POST via HTTPS with the response's status code 400 and
* non-empty response's body
*/
testCase = [testClass new];
[testCase setDebug: debug];
// the reference set difference (from the default reference set) we expect
// the flags must not be set because we request a path with no authorization
// See NSURLConnectionTest.h for details (the key words are 'TestCase' and 'ReferenceFlags')
refs = [NSDictionary dictionaryWithObjectsAndKeys:
@"NO", @"GOTUNAUTHORIZED",
@"NO", @"AUTHORIZED",
@"NO", @"NOTAUTHORIZED",
nil];
// the extra dictionary with test case's parameters
d = [NSDictionary dictionaryWithObjectsAndKeys:
server, @"Instance", // we use the shared TestWebServer instance
@"400/withoutauth", @"Path", // request the handler responding with 400
@"400", @"StatusCode", // the expected status code
@"You have issued a request with invalid data", @"Content", // the expected response's body
@"Some payload", @"Payload", // the custom payload
@"POST", @"Method", // use POST
refs, @"ReferenceFlags", // the expected reference set difference
nil];
[testCase setUpTest: d];
[testCase startTest: d];
PASS([testCase isSuccess], "HTTPS... no auth... payload... response 400 .... POST https://localhost:54321/400/withoutauth");
[testCase tearDownTest: d];
DESTROY(testCase);
/*
* Tests redirecting... it uses an auxilliary TestWebServer instance and proceeds
* in two stages. The first one is to get the status code 301 and go to the URL
* given in the response's header 'Location'. The second stage is a simple GET on
* the given URL with the status code 204 and empty response's body.
*/
testCase = [testClass new];
[testCase setDebug: debug];
// the reference set difference (from the default reference set) we expect
// the flags must not be set because we request a path with no authorization
// See NSURLConnectionTest.h for details (the key words are 'TestCase' and 'ReferenceFlags')
refs = [NSDictionary dictionaryWithObjectsAndKeys:
@"NO", @"GOTUNAUTHORIZED",
@"NO", @"AUTHORIZED",
@"NO", @"NOTAUTHORIZED",
@"YES", @"GOTREDIRECT",
nil];
// the extra dictionary with test case's parameters
d = [NSDictionary dictionaryWithObjectsAndKeys:
server, @"Instance", // we use the shared TestWebServer instance
@"/301/withoutauth", @"Path", // request the handler responding with a redirect
@"/withoutauth", @"RedirectPath", // the URL's path of redirecting
@"YES", @"IsAuxilliary", // start an auxilliary TestWebServer instance
refs, @"ReferenceFlags", // the expected reference set difference
nil];
[testCase setUpTest: d];
[testCase startTest: d];
PASS([testCase isSuccess], "HTTPS... no auth... redirecting... GET https://localhost:54321/301/withoutauth");
[testCase tearDownTest: d];
DESTROY(testCase);
// cleaning
[server stop];
DESTROY(server);
}
else
{
// no classes no tests
[NSException raise: NSInternalInconsistencyException
format: @"can't load bundle TestConnection"];
}
DESTROY(arp);
return 0;
}