TwitterNetworkLayerTests/TNLParameterCollectionTests.m (595 lines of code) (raw):

// // TNLParameterCollectionTests.m // TwitterNetworkLayer // // Created on 10/27/14. // Copyright © 2020 Twitter. All rights reserved. // #include <objc/runtime.h> #import "NSNumber+TNLURLCoding.h" #import "TNL_Project.h" #import "TNLParameterCollection.h" @import XCTest; #define PATH @"http://www.something.com/some/path" #define PARAMS_STRING @";one=1&two=&three&snowman=%E2%98%83&emoji=%E2%9B%84%EF%B8%8F" #define QUERY_STRING @"?une=1&deux=&trois&char=%26&array_of_dictionaries=%5B%7B%22key1%22%3A%22value1%22%2C%22key2%22%3A2%7D%2C%20%7B%22key3%22%3Anull%7D%5D" #define FRAGMENT_STRING @"#uno=1&dos=&tres&inner_url=http%3A%2F%2Fwww.something.com%2Fother%2Fpath%3Fextra%3Dinfo&z" #define ARG_COUNT (5UL) @interface TestBenignAssertionHandler : NSAssertionHandler @property (nonatomic, readonly) NSUInteger assertCount; @end @interface InvalidString : NSProxy @end static BOOL sSupportCustomEncoding = NO; static void TestSwizzle(Class cls, SEL originalSelector, SEL swizzledSelector) { Method originalMethod = class_getInstanceMethod(cls, originalSelector); Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector); BOOL didAddMethod = class_addMethod(cls, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(cls, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } } @interface NSURL (TestMethod) - (NSString *)test_tnl_URLEncodableStringValue; @end @interface NSArray (TestMethod) - (NSString *)test_tnl_URLEncodableStringValue; @end @implementation NSURL (TestMethod) - (NSString *)test_tnl_URLEncodableStringValue { if (!sSupportCustomEncoding) { return nil; } return [self absoluteString]; } @end @implementation NSArray (TestMethod) - (NSString *)test_tnl_URLEncodableStringValue { if (!sSupportCustomEncoding) { return nil; } NSMutableArray *subarray = [NSMutableArray arrayWithCapacity:self.count]; for (id item in self) { NSString *val; if ([item isKindOfClass:[NSString class]]) { val = item; } else if ([item respondsToSelector:@selector(tnl_URLEncodableStringValue)]) { val = [item tnl_URLEncodableStringValue]; } else if ([item respondsToSelector:@selector(stringValue)]) { val = [item stringValue]; } if (val.length > 0) { [subarray addObject:val]; } } [subarray sortUsingSelector:@selector(compare:)]; return [subarray componentsJoinedByString:@","]; } @end @interface TNLParameterCollectionTests : XCTestCase <TNLURLEncodableObject> @property (nonatomic, readonly) NSURL *pathURL; @property (nonatomic, readonly) NSURL *paramURL; @property (nonatomic, readonly) NSURL *queryURL; @property (nonatomic, readonly) NSURL *fragmentURL; @property (nonatomic, readonly) NSURL *allPartsURL; - (void)executeTestWithParams:(TNLParameterCollection *)params options:(TNLURLEncodableDictionaryOptions)options supportingCustom:(BOOL)supportingCustom expected:(NSDictionary *)expected; @end @implementation TNLParameterCollectionTests + (void)setUpTearDown { TestSwizzle([NSURL class], @selector(tnl_URLEncodableStringValue), @selector(test_tnl_URLEncodableStringValue)); TestSwizzle([NSArray class], @selector(tnl_URLEncodableStringValue), @selector(test_tnl_URLEncodableStringValue)); } + (void)setUp { [self setUpTearDown]; } + (void)tearDown { [self setUpTearDown]; } - (void)setUp { [super setUp]; _pathURL = [NSURL URLWithString:PATH]; _paramURL = [NSURL URLWithString:PATH PARAMS_STRING]; _queryURL = [NSURL URLWithString:PATH QUERY_STRING]; _fragmentURL = [NSURL URLWithString:PATH FRAGMENT_STRING]; _allPartsURL = [NSURL URLWithString:PATH PARAMS_STRING QUERY_STRING FRAGMENT_STRING]; } - (void)runCategoryTest:(NSURL *)url expectParamCount:(NSUInteger)expectParamCount expectQueryCount:(NSUInteger)expectQueryCount expectFragmentCount:(NSUInteger)expectFragmentCount { TNLParameterCollection *paramStringCollection = url.tnl_parameterStringCollection; TNLParameterCollection *queryCollection = url.tnl_queryCollection; TNLParameterCollection *fragmentCollection = url.tnl_fragmentCollection; XCTAssertEqual(paramStringCollection.count, expectParamCount); XCTAssertEqual(queryCollection.count, expectQueryCount); XCTAssertEqual(fragmentCollection.count, expectFragmentCount); if (expectParamCount) { XCTAssertEqualObjects(paramStringCollection[@"one"], @"1"); XCTAssertEqualObjects(paramStringCollection[@"two"], @""); XCTAssertEqualObjects(paramStringCollection[@"three"], @""); XCTAssertEqualObjects(paramStringCollection[@"snowman"], @"☃"); XCTAssertEqualObjects(paramStringCollection[@"emoji"], @"⛄️"); NSDictionary *d = @{ @"one" : @"1", @"two" : @"", @"three" : @"", @"snowman" : @"☃", @"emoji" : @"⛄️"}; XCTAssertEqualObjects([paramStringCollection underlyingDictionaryValue], d); XCTAssertEqualObjects([paramStringCollection stableURLEncodedStringValue], @"emoji=%E2%9B%84%EF%B8%8F&one=1&snowman=%E2%98%83&three=&two="); XCTAssertEqualObjects([NSSet setWithArray:d.allKeys], [NSSet setWithArray:paramStringCollection.allKeys]); } if (expectQueryCount) { XCTAssertEqualObjects(queryCollection[@"une"], @"1"); XCTAssertEqualObjects(queryCollection[@"deux"], @""); XCTAssertEqualObjects(queryCollection[@"trois"], @""); XCTAssertEqualObjects(queryCollection[@"char"], @"&"); XCTAssertEqualObjects(queryCollection[@"array_of_dictionaries"], @"[{\"key1\":\"value1\",\"key2\":2}, {\"key3\":null}]"); NSDictionary *d = @{ @"une" : @"1", @"deux" : @"", @"trois" : @"", @"char" : @"&", @"array_of_dictionaries" : @"[{\"key1\":\"value1\",\"key2\":2}, {\"key3\":null}]" }; XCTAssertEqualObjects([queryCollection underlyingDictionaryValue], d); XCTAssertEqualObjects([queryCollection stableURLEncodedStringValue], @"array_of_dictionaries=%5B%7B%22key1%22%3A%22value1%22%2C%22key2%22%3A2%7D%2C%20%7B%22key3%22%3Anull%7D%5D&char=%26&deux=&trois=&une=1"); XCTAssertEqualObjects([NSSet setWithArray:d.allKeys], [NSSet setWithArray:queryCollection.allKeys]); } if (expectFragmentCount) { XCTAssertEqualObjects(fragmentCollection[@"uno"], @"1"); XCTAssertEqualObjects(fragmentCollection[@"dos"], @""); XCTAssertEqualObjects(fragmentCollection[@"tres"], @""); XCTAssertEqualObjects(fragmentCollection[@"inner_url"], @"http://www.something.com/other/path?extra=info"); XCTAssertEqualObjects(fragmentCollection[@"z"], @""); NSDictionary *d = @{ @"uno" : @"1", @"dos" : @"", @"tres" : @"", @"inner_url" : @"http://www.something.com/other/path?extra=info", @"z" : @"" }; XCTAssertEqualObjects([fragmentCollection underlyingDictionaryValue], d); XCTAssertEqualObjects([fragmentCollection stableURLEncodedStringValue], @"dos=&inner_url=http%3A%2F%2Fwww.something.com%2Fother%2Fpath%3Fextra%3Dinfo&tres=&uno=1&z="); XCTAssertEqualObjects([NSSet setWithArray:d.allKeys], [NSSet setWithArray:fragmentCollection.allKeys]); } } - (void)testNULLsWithGetter { XCTAssertNoThrow([self runCategoryTest:nil expectParamCount:0 expectQueryCount:0 expectFragmentCount:0]); XCTAssertNoThrow(self.allPartsURL.tnl_parameterStringCollection); XCTAssertNoThrow(self.allPartsURL.tnl_queryCollection); XCTAssertNoThrow(self.allPartsURL.tnl_fragmentCollection); } - (void)testNilNSURLCategory { [self runCategoryTest:self.pathURL expectParamCount:0 expectQueryCount:0 expectFragmentCount:0]; } - (void)testPathNSURLCategory { [self runCategoryTest:self.pathURL expectParamCount:0 expectQueryCount:0 expectFragmentCount:0]; } - (void)testParameterStringNSURLCategory { [self runCategoryTest:self.paramURL expectParamCount:ARG_COUNT expectQueryCount:0 expectFragmentCount:0]; } - (void)testQueryNSURLCategory { [self runCategoryTest:self.queryURL expectParamCount:0 expectQueryCount:ARG_COUNT expectFragmentCount:0]; } - (void)testFragmentNSURLCategory { [self runCategoryTest:self.fragmentURL expectParamCount:0 expectQueryCount:0 expectFragmentCount:ARG_COUNT]; } - (void)testAllNSURLCategory { [self runCategoryTest:self.allPartsURL expectParamCount:ARG_COUNT expectQueryCount:ARG_COUNT expectFragmentCount:ARG_COUNT]; } - (void)testMutation { TNLMutableParameterCollection *params; NSString *paramStringOld; NSString *paramStringCurrent; params = [[self.allPartsURL tnl_parameterStringCollection] mutableCopy]; paramStringCurrent = params.stableURLEncodedStringValue; paramStringOld = paramStringCurrent; XCTAssertEqual(params.count, ARG_COUNT); XCTAssertEqualObjects(paramStringOld, paramStringCurrent); paramStringOld = paramStringCurrent; [params addParametersFromURL:self.pathURL parsingParameterTypes:TNLParameterTypeURLFragment | TNLParameterTypeURLParameterString | TNLParameterTypeURLQuery options:0]; paramStringCurrent = params.stableURLEncodedStringValue; XCTAssertEqual(params.count, ARG_COUNT); XCTAssertEqualObjects(paramStringOld, paramStringCurrent); paramStringOld = paramStringCurrent; [params addParametersFromURL:self.allPartsURL parsingParameterTypes:TNLParameterTypeNone options:0]; paramStringCurrent = params.stableURLEncodedStringValue; XCTAssertEqual(params.count, ARG_COUNT); XCTAssertEqualObjects(paramStringOld, paramStringCurrent); paramStringOld = paramStringCurrent; [params addParametersFromParameterCollection:params combineRepeatingKeys:NO]; paramStringCurrent = params.stableURLEncodedStringValue; XCTAssertEqual(params.count, ARG_COUNT); XCTAssertEqualObjects(paramStringOld, paramStringCurrent); paramStringOld = paramStringCurrent; [params addParametersFromURL:self.allPartsURL parsingParameterTypes:TNLParameterTypeURLQuery options:0]; paramStringCurrent = params.stableURLEncodedStringValue; XCTAssertEqual(params.count, ARG_COUNT * 2); XCTAssertNotEqualObjects(paramStringOld, paramStringCurrent); paramStringOld = paramStringCurrent; [params addParametersFromParameterCollection:self.allPartsURL.tnl_fragmentCollection combineRepeatingKeys:NO]; paramStringCurrent = params.stableURLEncodedStringValue; XCTAssertEqual(params.count, ARG_COUNT * 3); XCTAssertNotEqualObjects(paramStringOld, paramStringCurrent); paramStringOld = paramStringCurrent; params[@"ten"] = @"10"; params[@"nine"] = @"9"; params[@"eight"] = @"8"; XCTAssertTrue([@"8" isEqualToString:params[@"eight"]]); XCTAssertNoThrow((params[@"eight"] = nil)); // don't throw on nil entry, just remove XCTAssertTrue(nil == params[@"eight"]); params[@"eight"] = @"8"; XCTAssertTrue([@"8" isEqualToString:params[@"eight"]]); params[@"seven"] = @"7"; params[@"six"] = @"6"; paramStringCurrent = params.stableURLEncodedStringValue; XCTAssertEqual(params.count, ARG_COUNT * 4); XCTAssertNotEqualObjects(paramStringOld, paramStringCurrent); paramStringOld = paramStringCurrent; } - (void)testOptions { NSString *encodedString; TNLMutableParameterCollection *params = [[TNLMutableParameterCollection alloc] init]; params[@"zero"] = @"zero"; params[@"one"] = @"1"; params[@"two"] = @2; params[@"three"] = @3.14; params[@"four"] = @YES; params[@"five"] = self; params[@"six"] = [[NSScanner alloc] init]; params[@"seven"] = @""; params[@"eight"] = [@300 tnl_booleanObject]; params[@"nine"] = [NSURL URLWithString:@"http://www.twitter.com/jack"]; params[@"ten"] = params.underlyingDictionaryValue.allValues; encodedString = [params URLEncodedStringValueWithOptions:TNLURLEncodingOptionIgnoreUnsupportedValues | TNLURLEncodingOptionStableOrder]; XCTAssertEqualObjects(encodedString, @"eight=true&four=1&one=1&seven=&three=3.14&two=2&zero=zero"); encodedString = [params URLEncodedStringValueWithOptions:TNLURLEncodingOptionIgnoreUnsupportedValues | TNLURLEncodingOptionStableOrder | TNLURLEncodingOptionTrimEmptyValueDelimiter]; XCTAssertEqualObjects(encodedString, @"eight=true&four=1&one=1&seven&three=3.14&two=2&zero=zero"); XCTAssertThrows([params URLEncodedStringValueWithOptions:TNLURLEncodingOptionStableOrder]); encodedString = [params URLEncodedStringValueWithOptions:TNLURLEncodingOptionIgnoreUnsupportedValues | TNLURLEncodingOptionStableOrder | TNLURLEncodingOptionTreatUnsupportedValuesAsEmpty]; XCTAssertEqualObjects(encodedString, @"eight=true&five=&four=1&nine=&one=1&seven=&six=&ten=&three=3.14&two=2&zero=zero"); encodedString = [params URLEncodedStringValueWithOptions:TNLURLEncodingOptionStableOrder | TNLURLEncodingOptionTreatUnsupportedValuesAsEmpty]; XCTAssertEqualObjects(encodedString, @"eight=true&five=&four=1&nine=&one=1&seven=&six=&ten=&three=3.14&two=2&zero=zero"); encodedString = [params URLEncodedStringValueWithOptions:TNLURLEncodingOptionIgnoreUnsupportedValues | TNLURLEncodingOptionStableOrder | TNLURLEncodingOptionTreatUnsupportedValuesAsEmpty | TNLURLEncodingOptionTrimEmptyValueDelimiter]; XCTAssertEqualObjects(encodedString, @"eight=true&five&four=1&nine&one=1&seven&six&ten&three=3.14&two=2&zero=zero"); sSupportCustomEncoding = YES; encodedString = [params URLEncodedStringValueWithOptions:TNLURLEncodingOptionIgnoreUnsupportedValues | TNLURLEncodingOptionStableOrder]; XCTAssertEqualObjects(encodedString, @"eight=true&five=TNLParameterCollectionTests&four=1&nine=http%3A%2F%2Fwww.twitter.com%2Fjack&one=1&seven=&ten=1%2C1%2C2%2C3.14%2CTNLParameterCollectionTests%2Chttp%3A%2F%2Fwww.twitter.com%2Fjack%2Ctrue%2Czero&three=3.14&two=2&zero=zero"); sSupportCustomEncoding = NO; encodedString = [params URLEncodedStringValueWithOptions:TNLURLEncodingOptionIgnoreUnsupportedValues | TNLURLEncodingOptionStableOrder | TNLURLEncodingOptionDuplicateEntriesForArrayValues]; XCTAssertEqualObjects(encodedString, @"eight=true&four=1&one=1&seven=&ten=&ten=1&ten=1&ten=2&ten=3.14&ten=true&ten=zero&three=3.14&two=2&zero=zero"); encodedString = [params URLEncodedStringValueWithOptions:TNLURLEncodingOptionIgnoreUnsupportedValues | TNLURLEncodingOptionStableOrder | TNLURLEncodingOptionTrimEmptyValueDelimiter | TNLURLEncodingOptionDuplicateEntriesForArrayValues]; XCTAssertEqualObjects(encodedString, @"eight=true&four=1&one=1&seven&ten&ten=1&ten=1&ten=2&ten=3.14&ten=true&ten=zero&three=3.14&two=2&zero=zero"); encodedString = [params URLEncodedStringValueWithOptions:TNLURLEncodingOptionIgnoreUnsupportedValues | TNLURLEncodingOptionStableOrder | TNLURLEncodingOptionTreatUnsupportedValuesAsEmpty | TNLURLEncodingOptionDuplicateEntriesForArrayValues]; XCTAssertEqualObjects(encodedString, @"eight=true&five=&four=1&nine=&one=1&seven=&six=&ten=&ten=&ten=&ten=&ten=1&ten=1&ten=2&ten=3.14&ten=true&ten=zero&three=3.14&two=2&zero=zero"); encodedString = [params URLEncodedStringValueWithOptions:TNLURLEncodingOptionIgnoreUnsupportedValues | TNLURLEncodingOptionStableOrder | TNLURLEncodingOptionTreatUnsupportedValuesAsEmpty | TNLURLEncodingOptionTrimEmptyValueDelimiter | TNLURLEncodingOptionDuplicateEntriesForArrayValues]; XCTAssertEqualObjects(encodedString, @"eight=true&five&four=1&nine&one=1&seven&six&ten&ten&ten&ten&ten=1&ten=1&ten=2&ten=3.14&ten=true&ten=zero&three=3.14&two=2&zero=zero"); encodedString = [params URLEncodedStringValueWithOptions:TNLURLEncodingOptionIgnoreUnsupportedValues | TNLURLEncodingOptionStableOrder | TNLURLEncodingOptionDiscardEmptyValues | TNLURLEncodingOptionTreatUnsupportedValuesAsEmpty | TNLURLEncodingOptionDuplicateEntriesForArrayValues]; XCTAssertEqualObjects(encodedString, @"eight=true&four=1&one=1&ten=1&ten=1&ten=2&ten=3.14&ten=true&ten=zero&three=3.14&two=2&zero=zero"); sSupportCustomEncoding = YES; encodedString = [params URLEncodedStringValueWithOptions:TNLURLEncodingOptionIgnoreUnsupportedValues | TNLURLEncodingOptionStableOrder | TNLURLEncodingOptionDuplicateEntriesForArrayValues]; XCTAssertEqualObjects(encodedString, @"eight=true&five=TNLParameterCollectionTests&four=1&nine=http%3A%2F%2Fwww.twitter.com%2Fjack&one=1&seven=&ten=&ten=1&ten=1&ten=2&ten=3.14&ten=TNLParameterCollectionTests&ten=http%3A%2F%2Fwww.twitter.com%2Fjack&ten=true&ten=zero&three=3.14&two=2&zero=zero"); sSupportCustomEncoding = NO; encodedString = [params URLEncodedStringValueWithOptions:TNLURLEncodingOptionIgnoreUnsupportedValues | TNLURLEncodingOptionStableOrder | TNLURLEncodingOptionDiscardEmptyValues | TNLURLEncodingOptionTreatUnsupportedValuesAsEmpty | TNLURLEncodingOptionDuplicateEntriesForArrayValues | TNLURLEncodingOptionEncodeBooleanNumbersAsTrueOrFalse]; XCTAssertEqualObjects(encodedString, @"eight=true&four=true&one=1&ten=1&ten=2&ten=3.14&ten=true&ten=true&ten=zero&three=3.14&two=2&zero=zero"); // URL Encoding of value yields NULL instead of valid NSString...rare case that is being triggered for some users. sSupportCustomEncoding = NO; #if DEBUG TNLSetDebugSTOPOnAssertEnabled(NO); #endif TestBenignAssertionHandler *newHandler = [[TestBenignAssertionHandler alloc] init]; NSAssertionHandler *oldHandler = [[NSThread currentThread] threadDictionary][NSAssertionHandlerKey]; [[NSThread currentThread] threadDictionary][NSAssertionHandlerKey] = newHandler; NSString *str = [(id)[InvalidString alloc] init]; params[@"another"] = str; encodedString = [params URLEncodedStringValueWithOptions:TNLURLEncodingOptionIgnoreUnsupportedValues | TNLURLEncodingOptionStableOrder | TNLURLEncodingOptionDuplicateEntriesForArrayValues]; XCTAssertEqualObjects(encodedString, @"eight=true&four=1&one=1&seven=&ten=&ten=1&ten=1&ten=2&ten=3.14&ten=true&ten=zero&three=3.14&two=2&zero=zero"); XCTAssertGreaterThan(newHandler.assertCount, (NSUInteger)0); if (oldHandler) { [[NSThread currentThread] threadDictionary][NSAssertionHandlerKey] = oldHandler; } else { [[[NSThread currentThread] threadDictionary] removeObjectForKey:NSAssertionHandlerKey]; } #if DEBUG TNLSetDebugSTOPOnAssertEnabled(YES); #endif sSupportCustomEncoding = NO; #if DEBUG TNLSetDebugSTOPOnAssertEnabled(NO); #endif str = @"abc🐱abc"; str = [str substringWithRange:NSMakeRange(4,2)]; newHandler = [[TestBenignAssertionHandler alloc] init]; oldHandler = [[NSThread currentThread] threadDictionary][NSAssertionHandlerKey]; [[NSThread currentThread] threadDictionary][NSAssertionHandlerKey] = newHandler; params[@"another"] = str; encodedString = [params URLEncodedStringValueWithOptions:TNLURLEncodingOptionIgnoreUnsupportedValues | TNLURLEncodingOptionStableOrder | TNLURLEncodingOptionDuplicateEntriesForArrayValues]; XCTAssertEqualObjects(encodedString, @"eight=true&four=1&one=1&seven=&ten=&ten=1&ten=1&ten=2&ten=3.14&ten=true&ten=zero&three=3.14&two=2&zero=zero"); XCTAssertGreaterThan(newHandler.assertCount, (NSUInteger)0); if (oldHandler) { [[NSThread currentThread] threadDictionary][NSAssertionHandlerKey] = oldHandler; } else { [[[NSThread currentThread] threadDictionary] removeObjectForKey:NSAssertionHandlerKey]; } #if DEBUG TNLSetDebugSTOPOnAssertEnabled(YES); #endif } - (void)testURLEncodableDictionary { TNLMutableParameterCollection *params = [[TNLMutableParameterCollection alloc] init]; params[@"zero"] = @"zero"; params[@"one"] = @"1"; params[@"two"] = @2; params[@"three"] = @3.14; params[@"four"] = @YES; params[@"five"] = self; params[@"six"] = [[NSScanner alloc] init]; params[@"seven"] = @""; params[@"eight"] = @[@"1", @2, @[ @"3", @4] ]; params[@"nine"] = @{ @"5" : @6, @"7" : @{ @"8" : @9 } }; params[@"ten"] = [NSURL URLWithString:@"http://www.twitter.com/jack"]; NSDictionary *expected = nil; TNLURLEncodableDictionaryOptions options = TNLURLEncodableDictionaryOptionsNone; XCTAssertThrows([self executeTestWithParams:params options:options supportingCustom:NO expected:nil]); XCTAssertThrows([self executeTestWithParams:params options:options supportingCustom:YES expected:nil]); XCTAssertFalse([TNLURLEncodableDictionary(@{ @"1" : @"2" }, 0) respondsToSelector:@selector(setObject:forKey:)]); XCTAssertTrue([TNLURLEncodableDictionary(@{ @"1" : @"2" }, TNLURLEncodableDictionaryOptionOutputMutableDictionary) respondsToSelector:@selector(setObject:forKey:)]); // options = TNLURLEncodableDictionaryOptionIgnoreUnsupportedValues; expected = @{ @"zero" : @"zero", @"one" : @"1", @"two" : @2, @"three" : @3.14, @"four" : @YES, @"seven" : @"", }; [self executeTestWithParams:params options:options supportingCustom:NO expected:expected]; expected = @{ @"zero" : @"zero", @"one" : @"1", @"two" : @2, @"three" : @3.14, @"four" : @YES, @"five" : @"TNLParameterCollectionTests", @"seven" : @"", @"eight" : @"1,2,3,4", @"ten" : @"http://www.twitter.com/jack", }; [self executeTestWithParams:params options:options supportingCustom:YES expected:expected]; // options = TNLURLEncodableDictionaryOptionTreatUnsupportedValuesAsEmpty | TNLURLEncodableDictionaryOptionDiscardEmptyValues; expected = @{ @"zero" : @"zero", @"one" : @"1", @"two" : @2, @"three" : @3.14, @"four" : @YES, }; [self executeTestWithParams:params options:options supportingCustom:NO expected:expected]; expected = @{ @"zero" : @"zero", @"one" : @"1", @"two" : @2, @"three" : @3.14, @"four" : @YES, @"five" : @"TNLParameterCollectionTests", @"eight" : @"1,2,3,4", @"ten" : @"http://www.twitter.com/jack", }; [self executeTestWithParams:params options:options supportingCustom:YES expected:expected]; // options = TNLURLEncodableDictionaryOptionTreatUnsupportedValuesAsEmpty; expected = @{ @"zero" : @"zero", @"one" : @"1", @"two" : @2, @"three" : @3.14, @"four" : @YES, @"five" : @"", @"six" : @"", @"seven" : @"", @"eight" : @"", @"nine" : @"", @"ten" : @"", }; [self executeTestWithParams:params options:options supportingCustom:NO expected:expected]; expected = @{ @"zero" : @"zero", @"one" : @"1", @"two" : @2, @"three" : @3.14, @"four" : @YES, @"five" : @"TNLParameterCollectionTests", @"six" : @"", @"seven" : @"", @"eight" : @"1,2,3,4", @"nine" : @"", @"ten" : @"http://www.twitter.com/jack", }; [self executeTestWithParams:params options:options supportingCustom:YES expected:expected]; // options = TNLURLEncodableDictionaryOptionIgnoreUnsupportedValues | TNLURLEncodableDictionaryOptionReplaceArraysWithArraysOfEncodableStrings | TNLURLEncodableDictionaryOptionReplaceDictionariesWithDictionariesOfEncodableStrings; expected = @{ @"zero" : @"zero", @"one" : @"1", @"two" : @2, @"three" : @3.14, @"four" : @YES, @"seven" : @"", @"eight" : @[ @"1", @2, @[ @"3", @4 ]], @"nine" : @{ @"5" : @6, @"7" : @{ @"8" : @9 } } }; [self executeTestWithParams:params options:options supportingCustom:NO expected:expected]; expected = @{ @"zero" : @"zero", @"one" : @"1", @"two" : @2, @"three" : @3.14, @"four" : @YES, @"five" : @"TNLParameterCollectionTests", @"seven" : @"", @"eight" : @[ @"1", @2, @[ @"3", @4 ]], @"nine" : @{ @"5" : @6, @"7" : @{ @"8" : @9 } }, @"ten" : @"http://www.twitter.com/jack", }; [self executeTestWithParams:params options:options supportingCustom:YES expected:expected]; } - (void)executeTestWithParams:(TNLParameterCollection *)params options:(TNLURLEncodableDictionaryOptions)options supportingCustom:(BOOL)supportingCustom expected:(NSDictionary *)expected { sSupportCustomEncoding = supportingCustom; NSException *exception = nil; @try { NSDictionary *dict = [params encodableDictionaryValueWithOptions:options]; XCTAssertEqualObjects(expected, dict, @"options = 0x%zx", (long)options); } @catch (NSException *e) { exception = e; } sSupportCustomEncoding = NO; if (exception) { @throw exception; // rethrow } } - (void)testEmptyKey { TNLMutableParameterCollection *params = [[TNLMutableParameterCollection alloc] init]; XCTAssertNoThrow([params setParameterValue:@0 forKey:@"0"]); XCTAssertThrows([params setParameterValue:@0 forKey:(NSString * __nonnull)nil]); XCTAssertThrows([params setParameterValue:@0 forKey:@""]); XCTAssertThrows([params setParameterValue:@0 forKey:(NSString *)@0]); XCTAssertEqual((NSUInteger)1, (NSUInteger)params.count); } - (void)testAddingDictionaries { TNLMutableParameterCollection *params = [[TNLMutableParameterCollection alloc] init]; NSDictionary *dict = @{ @"key1" : @"value1", @"key3" : @"value3", @"key2" : @"value2", }; NSString *encodedString; // 1) Use Keys Directly [params removeAllParameters]; [params addParametersFromDictionary:dict withFormattingMode:TNLParameterCollectionAddParametersFromDictionaryModeUseKeysDirectly combineRepeatingKeys:NO forKey:@"dict"]; encodedString = [params stableURLEncodedStringValue]; XCTAssertEqualObjects(encodedString, @"key1=value1&key2=value2&key3=value3"); // 2) URL Encoded [params removeAllParameters]; [params addParametersFromDictionary:dict withFormattingMode:TNLParameterCollectionAddParametersFromDictionaryModeURLEncoded combineRepeatingKeys:NO forKey:@"dict"]; encodedString = [params stableURLEncodedStringValue]; XCTAssertEqualObjects(encodedString, @"dict=key1%3Dvalue1%26key2%3Dvalue2%26key3%3Dvalue3"); // 3) JSON Encoded [params removeAllParameters]; [params addParametersFromDictionary:dict withFormattingMode:TNLParameterCollectionAddParametersFromDictionaryModeJSONEncoded combineRepeatingKeys:NO forKey:@"dict"]; encodedString = [params stableURLEncodedStringValue]; if (tnl_available_ios_11) { // will be sorted XCTAssertEqualObjects(encodedString, @"dict=%7B%22key1%22%3A%22value1%22%2C%22key2%22%3A%22value2%22%2C%22key3%22%3A%22value3%22%7D"); } else { // unsorted, need to check all variations :( #define CHECK_STRING(first, second, third) @"dict=%7B%22key" #first "%22%3A%22value" #first "%22%2C%22key" #second "%22%3A%22value" #second "%22%2C%22key" #third "%22%3A%22value" #third "%22%7D" if (!encodedString) { XCTAssertNotNil(encodedString); } else if ([encodedString isEqualToString:CHECK_STRING(1, 2, 3)]) { XCTAssertTrue(YES); } else if ([encodedString isEqualToString:CHECK_STRING(1, 3, 2)]) { XCTAssertTrue(YES); } else if ([encodedString isEqualToString:CHECK_STRING(2, 1, 3)]) { XCTAssertTrue(YES); } else if ([encodedString isEqualToString:CHECK_STRING(2, 3, 1)]) { XCTAssertTrue(YES); } else if ([encodedString isEqualToString:CHECK_STRING(3, 1, 2)]) { XCTAssertTrue(YES); } else if ([encodedString isEqualToString:CHECK_STRING(3, 2, 1)]) { XCTAssertTrue(YES); } else { XCTAssertTrue(NO, @"%@ is the wrong encodedString!", encodedString); } } #undef CHECK_STRING // 4) Dot-syntax [params removeAllParameters]; [params addParametersFromDictionary:dict withFormattingMode:TNLParameterCollectionAddParametersFromDictionaryModeDotSyntaxOnProvidedKey combineRepeatingKeys:NO forKey:@"dict"]; encodedString = [params stableURLEncodedStringValue]; XCTAssertEqualObjects(encodedString, @"dict.key1=value1&dict.key2=value2&dict.key3=value3"); } - (void)testUnderlyingVersusEncodableDictionaries { sSupportCustomEncoding = YES; tnl_defer(^{ sSupportCustomEncoding = NO; }); NSDictionary *rawD = @{ @"one" : @1, @"two" : @"2", @"three" : [@YES tnl_booleanObject], @"four" : [@NO tnl_booleanObject], @"five" : [@5 tnl_booleanObject], @"six" : @{ @"six.one" : @1, @"six.two" : @"2", @"six.three" : @YES, @"six.four" : @NO, @"six.five" : [@5 tnl_booleanObject], }, @"seven" : @[ @1, @"2", @YES, [@NO tnl_booleanObject], [@5 tnl_booleanObject] ], }; NSDictionary *expectedEncodedD = @{ @"one" : @1, @"two" : @"2", @"three" : @"true", @"four" : @"false", @"five" : @"true", @"six" : @{ @"six.one" : @1, @"six.two" : @"2", @"six.three" : @YES, @"six.four" : @NO, @"six.five" : @"true", }, @"seven" : @[ @1, @"2", @YES, @"false", @"true" ], }; TNLMutableParameterCollection *params = [[TNLMutableParameterCollection alloc] initWithDictionary:rawD]; NSDictionary *underlyingD; NSDictionary *encodedD; underlyingD = params.underlyingDictionaryValue; encodedD = params.encodableDictionaryValue; XCTAssertNotEqualObjects(underlyingD, encodedD); XCTAssertEqualObjects(underlyingD, rawD); XCTAssertEqualObjects(encodedD, expectedEncodedD); NSDate *date = [NSDate date]; params[@"eight"] = date; underlyingD = params.underlyingDictionaryValue; XCTAssertEqualObjects(underlyingD[@"eight"], date); XCTAssertThrows((void)params.encodableDictionaryValue); // cannot encode NSDate without TNLURLEncodableObject conformance } #pragma mark TNLURLEncodableObject - (NSString *)tnl_URLEncodableStringValue { return (sSupportCustomEncoding) ? NSStringFromClass([self class]) : nil; } @end @implementation TestBenignAssertionHandler - (void)handleFailureInMethod:(SEL)selector object:(id)object file:(NSString *)fileName lineNumber:(NSInteger)line description:(NSString *)format,... { _assertCount++; } - (void)handleFailureInFunction:(NSString *)functionName file:(NSString *)fileName lineNumber:(NSInteger)line description:(NSString *)format,... { _assertCount++; } @end @implementation InvalidString { NSString *_proxyString; } - (id)init { _proxyString = @"fake string"; return self; } - (void)forwardInvocation:(NSInvocation *)invocation { if (@selector(UTF8String) == invocation.selector || @selector(copyWithZone:) == invocation.selector || @selector(copy) == invocation.selector) { invocation.target = self; } else { invocation.target = _proxyString; } [invocation invoke]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { if (sel == @selector(UTF8String) || sel == @selector(copyWithZone:) || sel == @selector(copy)) { return [super methodSignatureForSelector:sel]; } return [_proxyString methodSignatureForSelector:sel]; } - (const char *)UTF8String { return NULL; } - (id)copyWithZone:(NSZone *)zone { return self; } - (id)copy { return [self copyWithZone:nil]; } @end