//
//  RKObjectMappingOperationTest.m
//  RestKit
//
//  Created by Blake Watters on 4/30/11.
//  Copyright (c) 2009-2012 RestKit. All rights reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//

#import "RKTestEnvironment.h"
#import "RKMappingErrors.h"
#import "RKMappableObject.h"
#import "RKMappableAssociation.h"
#import "RKObjectMappingOperationDataSource.h"
#import "RKTestAddress.h"
#import "RKTestUser.h"

@interface TestMappable : NSObject {
    NSURL *_url;
    NSString *_boolString;
    NSDate *_date;
    NSOrderedSet *_orderedSet;
    NSArray *_array;
}

@property (nonatomic, strong) NSURL *url;
@property (nonatomic, strong) NSString *boolString;
@property (nonatomic, strong) NSNumber *boolNumber;
@property (nonatomic, strong) NSDate *date;
@property (nonatomic, strong) NSOrderedSet *orderedSet;
@property (nonatomic, strong) NSArray *array;

@end

@implementation TestMappable

@synthesize url = _url;
@synthesize boolString = _boolString;
@synthesize boolNumber = _boolNumber;
@synthesize date = _date;
@synthesize orderedSet = _orderedSet;
@synthesize array = _array;

- (BOOL)validateBoolString:(id *)ioValue error:(NSError **)outError
{
    if ([(NSObject *)*ioValue isKindOfClass:[NSString class]] && [(NSString *)*ioValue isEqualToString:@"FAIL"]) {
        *outError = [NSError errorWithDomain:RKErrorDomain code:RKMappingErrorUnmappableRepresentation userInfo:nil];
        return NO;
    } else if ([(NSObject *)*ioValue isKindOfClass:[NSString class]] && [(NSString *)*ioValue isEqualToString:@"REJECT"]) {
        return NO;
    } else if ([(NSObject *)*ioValue isKindOfClass:[NSString class]] && [(NSString *)*ioValue isEqualToString:@"MODIFY"]) {
        *ioValue = @"modified value";
        return YES;
    }

    return YES;
}

@end

@interface RKObjectMappingOperationTest : RKTestCase {

}

@end

@implementation RKObjectMappingOperationTest

- (void)testShouldNotUpdateEqualURLProperties
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[TestMappable class]];
    [mapping addAttributeMappingsFromArray:@[@"url"]];
    NSURL *url1 = [NSURL URLWithString:@"http://www.restkit.org"];
    NSURL *url2 = [NSURL URLWithString:@"http://www.restkit.org"];
    assertThatBool(url1 == url2, is(equalToBool(NO)));
    TestMappable *object = [[TestMappable alloc] init];
    [object setUrl:url1];
    NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:url2, @"url", nil];

    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:object mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    [operation start];
    BOOL success = (operation.error == nil);
    assertThatBool(success, is(equalToBool(YES)));
    assertThatBool(object.url == url1, is(equalToBool(YES)));
}

- (void)testShouldSuccessfullyMapBoolsToStrings
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[TestMappable class]];
    [mapping addAttributeMappingsFromArray:@[@"boolString"]];
    TestMappable *object = [[TestMappable alloc] init];

    NSData *data = [@"{\"boolString\":true}" dataUsingEncoding:NSUTF8StringEncoding];
    id deserializedObject = [RKMIMETypeSerialization objectFromData:data MIMEType:RKMIMETypeJSON error:nil];

    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:deserializedObject destinationObject:object mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    [operation start];
    BOOL success = (operation.error == nil);
    assertThatBool(success, is(equalToBool(YES)));
    assertThat(object.boolString, is(equalTo(@"true")));
}

- (void)testShouldSuccessfullyMapTrueBoolsToNSNumbers
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[TestMappable class]];
    [mapping addAttributeMappingsFromArray:@[@"boolNumber"]];
    TestMappable *object = [[TestMappable alloc] init];

    NSData *data = [@"{\"boolNumber\":true}" dataUsingEncoding:NSUTF8StringEncoding];
    id deserializedObject = [RKMIMETypeSerialization objectFromData:data MIMEType:RKMIMETypeJSON error:nil];

    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:deserializedObject destinationObject:object mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    [operation start];
    BOOL success = (operation.error == nil);
    assertThatBool(success, is(equalToBool(YES)));
    assertThatInt([object.boolNumber intValue], is(equalToInt(1)));
}

- (void)testShouldSuccessfullyMapFalseBoolsToNSNumbers
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[TestMappable class]];
    [mapping addAttributeMappingsFromArray:@[@"boolNumber"]];
    TestMappable *object = [[TestMappable alloc] init];

    NSData *data = [@"{\"boolNumber\":false}" dataUsingEncoding:NSUTF8StringEncoding];
    id deserializedObject = [RKMIMETypeSerialization objectFromData:data MIMEType:RKMIMETypeJSON error:nil];

    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:deserializedObject destinationObject:object mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    [operation start];
    BOOL success = (operation.error == nil);
    assertThatBool(success, is(equalToBool(YES)));
    assertThatInt([object.boolNumber intValue], is(equalToInt(0)));
}

- (void)testShouldSuccessfullyMapNumbersToStrings
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[TestMappable class]];
    [mapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"number" toKeyPath:@"boolString"]];
    TestMappable *object = [[TestMappable alloc] init];

    NSData *data = [@"{\"number\":123}" dataUsingEncoding:NSUTF8StringEncoding];
    id deserializedObject = [RKMIMETypeSerialization objectFromData:data MIMEType:RKMIMETypeJSON error:nil];

    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:deserializedObject destinationObject:object mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    [operation start];
    BOOL success = (operation.error == nil);
    assertThatBool(success, is(equalToBool(YES)));
    assertThat(object.boolString, is(equalTo(@"123")));
}

- (void)testShouldSuccessfullyMapPropertiesBeforeKeyPathAttributes
{
    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    RKAttributeMapping *nestedCityMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"nested.city" toKeyPath:@"address.city"];
    [userMapping addPropertyMapping:nameMapping];
    [userMapping addPropertyMapping:nestedCityMapping];

    RKObjectMapping *addressMapping = [RKObjectMapping mappingForClass:[RKTestAddress class]];
    RKAttributeMapping *cityMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"city" toKeyPath:@"city"];
    [addressMapping addPropertyMapping:cityMapping];

    RKRelationshipMapping *hasOneMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:@"address" toKeyPath:@"address" withMapping:addressMapping];
    [userMapping addPropertyMapping:hasOneMapping];

    NSData *data = [@"{\"name\": \"Blake Watters\",\"address\": {\"city\": \"Carrboro\"},\"nested\": {\"city\": \"New York\"}}" dataUsingEncoding:NSUTF8StringEncoding];
    id deserializedObject = [RKMIMETypeSerialization objectFromData:data MIMEType:RKMIMETypeJSON error:nil];

    RKTestUser *user = [RKTestUser user];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:deserializedObject destinationObject:user mapping:userMapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    [operation start];
    BOOL success = (operation.error == nil);
    assertThatBool(success, is(equalToBool(YES)));
    assertThat(user.name, is(equalTo(@"Blake Watters")));
    assertThat(user.address.city, is(equalTo(@"New York")));
}

- (void)testShouldSuccessfullyMapArraysToOrderedSets
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[TestMappable class]];
    [mapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"numbers" toKeyPath:@"orderedSet"]];
    TestMappable *object = [[TestMappable alloc] init];

    NSData *data = [@"{\"numbers\":[1, 2, 3]}" dataUsingEncoding:NSUTF8StringEncoding];
    id deserializedObject = [RKMIMETypeSerialization objectFromData:data MIMEType:RKMIMETypeJSON error:nil];

    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:deserializedObject destinationObject:object mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    BOOL success = [operation performMapping:nil];
    assertThatBool(success, is(equalToBool(YES)));
    NSOrderedSet *expectedSet = [NSOrderedSet orderedSetWithObjects:[NSNumber numberWithInt:1], [NSNumber numberWithInt:2], [NSNumber numberWithInt:3], nil];
    assertThat(object.orderedSet, is(equalTo(expectedSet)));
}

- (void)testShouldSuccessfullyMapOrderedSetsToArrays
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[TestMappable class]];
    [mapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"orderedSet" toKeyPath:@"array"]];
    TestMappable *object = [[TestMappable alloc] init];

    TestMappable *data = [[TestMappable alloc] init];
    data.orderedSet = [NSOrderedSet orderedSetWithObjects:[NSNumber numberWithInt:1], [NSNumber numberWithInt:2], [NSNumber numberWithInt:3], nil];

    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:data destinationObject:object mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    BOOL success = [operation performMapping:nil];
    assertThatBool(success, is(equalToBool(YES)));
    NSArray *expectedArray = [NSArray arrayWithObjects:[NSNumber numberWithInt:1], [NSNumber numberWithInt:2], [NSNumber numberWithInt:3], nil];
    assertThat(object.array, is(equalTo(expectedArray)));
}

- (void)testShouldFailTheMappingOperationIfKeyValueValidationSetsAnError
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[TestMappable class]];
    [mapping addAttributeMappingsFromArray:@[@"boolString"]];
    TestMappable *object = [[TestMappable alloc] init];
    NSDictionary *dictionary = [NSDictionary dictionaryWithObject:@"FAIL" forKey:@"boolString"];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:object mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    BOOL success = [operation performMapping:&error];
    assertThatBool(success, is(equalToBool(NO)));
    assertThat(error, isNot(nilValue()));
}

- (void)testShouldNotSetTheAttributeIfKeyValueValidationReturnsNo
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[TestMappable class]];
    [mapping addAttributeMappingsFromArray:@[@"boolString"]];
    TestMappable *object = [[TestMappable alloc] init];
    object.boolString = @"should not change";
    NSDictionary *dictionary = [NSDictionary dictionaryWithObject:@"REJECT" forKey:@"boolString"];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:object mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    BOOL success = [operation performMapping:&error];
    assertThatBool(success, is(equalToBool(YES)));
    assertThat(object.boolString, is(equalTo(@"should not change")));
}

- (void)testModifyingValueWithinKeyValueValidationIsRespected
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[TestMappable class]];
    [mapping addAttributeMappingsFromArray:@[@"boolString"]];
    TestMappable *object = [[TestMappable alloc] init];
    NSDictionary *dictionary = [NSDictionary dictionaryWithObject:@"MODIFY" forKey:@"boolString"];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:object mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    BOOL success = [operation performMapping:&error];
    assertThatBool(success, is(equalToBool(YES)));
    assertThat(object.boolString, is(equalTo(@"modified value")));
}

#pragma mark - TimeZone Handling

- (void)testShouldMapAUTCDateWithoutChangingTheTimeZone
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[TestMappable class]];
    [mapping addAttributeMappingsFromArray:@[@"date"]];
    TestMappable *object = [[TestMappable alloc] init];
    NSDictionary *dictionary = [NSDictionary dictionaryWithObject:@"2011-07-07T04:35:28Z" forKey:@"date"];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:object mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    BOOL success = [operation performMapping:&error];
    assertThatBool(success, is(equalToBool(YES)));
    assertThat(object.date, isNot(nilValue()));
    assertThat([object.date description], is(equalTo(@"2011-07-07 04:35:28 +0000")));
}

- (void)testShouldMapAUnixTimestampStringAppropriately
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[TestMappable class]];
    [mapping addAttributeMappingsFromArray:@[@"date"]];
    TestMappable *object = [[TestMappable alloc] init];
    NSDictionary *dictionary = [NSDictionary dictionaryWithObject:@"457574400" forKey:@"date"];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:object mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    BOOL success = [operation performMapping:&error];
    assertThatBool(success, is(equalToBool(YES)));
    assertThat(object.date, isNot(nilValue()));
    assertThat([object.date description], is(equalTo(@"1984-07-02 00:00:00 +0000")));
}

- (void)testShouldMapASimpleDateStringAppropriately
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[TestMappable class]];
    [mapping addAttributeMappingsFromArray:@[@"date"]];
    TestMappable *object = [[TestMappable alloc] init];
    NSDictionary *dictionary = [NSDictionary dictionaryWithObject:@"08/09/2011" forKey:@"date"];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:object mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    BOOL success = [operation performMapping:&error];
    assertThatBool(success, is(equalToBool(YES)));
    assertThat(object.date, isNot(nilValue()));
    assertThat([object.date description], is(equalTo(@"2011-08-09 00:00:00 +0000")));
}

- (void)testShouldMapAISODateStringAppropriately
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[TestMappable class]];
    [mapping addAttributeMappingsFromArray:@[@"date"]];
    TestMappable *object = [[TestMappable alloc] init];
    NSDictionary *dictionary = [NSDictionary dictionaryWithObject:@"2011-08-09T00:00Z" forKey:@"date"];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:object mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    BOOL success = [operation performMapping:&error];
    assertThatBool(success, is(equalToBool(YES)));
    assertThat(object.date, isNot(nilValue()));
    assertThat([object.date description], is(equalTo(@"2011-08-09 00:00:00 +0000")));
}

- (void)testShouldMapAStringIntoTheLocalTimeZone
{
    NSTimeZone *EDTTimeZone = [NSTimeZone timeZoneWithAbbreviation:@"EDT"];
    NSDateFormatter *dateFormatter = [NSDateFormatter new];
    dateFormatter.dateFormat = @"MM-dd-yyyy";
    dateFormatter.timeZone = EDTTimeZone;
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[TestMappable class]];
    [mapping addAttributeMappingsFromArray:@[@"date"]];
    mapping.dateFormatters = [NSArray arrayWithObject:dateFormatter];
    TestMappable *object = [[TestMappable alloc] init];
    NSDictionary *dictionary = [NSDictionary dictionaryWithObject:@"11-27-1982" forKey:@"date"];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:object mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    BOOL success = [operation performMapping:&error];
    assertThatBool(success, is(equalToBool(YES)));
    assertThat(object.date, isNot(nilValue()));
    // Since there is no time date, our description will be midnight + UTC offset (5 hours)
    assertThat([object.date description], is(equalTo(@"1982-11-27 05:00:00 +0000")));
}

- (void)testShouldMapADateToAStringUsingThePreferredDateFormatter
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[TestMappable class]];
    [mapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"date" toKeyPath:@"boolString"]];
    NSDateFormatter *dateFormatter = [NSDateFormatter new];
    dateFormatter.dateFormat = @"MM-dd-yyyy";
    dateFormatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
    TestMappable *object = [[TestMappable alloc] init];
    object.date = [dateFormatter dateFromString:@"11-27-1982"];
    mapping.preferredDateFormatter = dateFormatter;
    TestMappable *newObject = [TestMappable new];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:object destinationObject:newObject mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    BOOL success = [operation performMapping:&error];
    assertThatBool(success, is(equalToBool(YES)));
    assertThat(newObject.boolString, is(equalTo(@"11-27-1982")));
}

- (void)testShouldLogADebugMessageIfTheRelationshipMappingTargetsAnArrayOfArrays
{
    // Create a dictionary with a dictionary containing an array
    // Use keyPath to traverse to the collection and target a hasMany
    id data = [RKTestFixture parsedObjectWithContentsOfFixture:@"ArrayOfNestedDictionaries.json"];
    RKObjectMapping *objectMapping = [RKObjectMapping mappingForClass:[RKMappableObject class]];
    [objectMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"stringTest"]];
    RKObjectMapping *relationshipMapping = [RKObjectMapping mappingForClass:[RKMappableAssociation class]];
    [relationshipMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"title" toKeyPath:@"testString"]];
    [objectMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"mediaGroups.contents" toKeyPath:@"hasMany" withMapping:relationshipMapping]];;
    RKMappableObject *targetObject = [RKMappableObject new];
    RKLogToComponentWithLevelWhileExecutingBlock(RKlcl_cRestKitObjectMapping, RKLogLevelDebug, ^ {
        RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:data
                                                                                   destinationObject:targetObject mapping:objectMapping];
        RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
        operation.dataSource = dataSource;
        NSError *error = nil;
        [operation performMapping:&error];
    });
}

- (void)testMappingSimpleAttributesDoesNotTriggerDataSourceAssertion
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
    [mapping addAttributeMappingsFromArray:@[@"boolString"]];
    TestMappable *object = [TestMappable new];
    object.boolString = @"test";
    NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:object destinationObject:dictionary mapping:mapping];
    NSException *exception = nil;
    @try {
        [operation start];
    }
    @catch (NSException *e) {
        exception = e;
    }
    @finally {
        expect(exception).to.beNil();
    }
}

- (void)testCancellationOfMapperOperation
{
    RKObjectMapping *childMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [childMapping addAttributeMappingsFromArray:@[@"name"]];
    
    RKObjectMapping *parentMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [parentMapping addAttributeMappingsFromArray:@[@"name"]];
    [parentMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"children" toKeyPath:@"friends" withMapping:childMapping]];
    NSDictionary *mappingsDictionary = @{ @"parents": parentMapping };
    
    NSOperationQueue *operationQueue = [NSOperationQueue new];
    NSDictionary *JSON = [RKTestFixture parsedObjectWithContentsOfFixture:@"benchmark_parents_and_children.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:JSON mappingsDictionary:mappingsDictionary];
    [operationQueue addOperation:mapper];
    [mapper cancel];
    [operationQueue waitUntilAllOperationsAreFinished];
    expect([mapper isCancelled]).to.equal(YES);
    expect(mapper.error).to.beNil();
    expect(mapper.mappingResult).to.beNil();
}

- (void)testMappingRootKeyToDictionary
{
    NSDictionary *representation = @{ @"MyObject": @{ @"ObjectAttribute1": @{} }, @"MyRootString": @"SomeString" };
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
    [mapping addAttributeMappingsFromDictionary:@{ @"MyRootString": @"MyRootString" }];
    
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:representation destinationObject:nil mapping:mapping];
    mappingOperation.dataSource = dataSource;
    BOOL success = [mappingOperation performMapping:nil];
    expect(success).to.equal(YES);
    expect([mappingOperation.destinationObject isKindOfClass:[NSMutableDictionary class]]).to.equal(YES);
    expect([mappingOperation.destinationObject valueForKeyPath:@"MyRootString"]).to.equal(@"SomeString");
}

- (void)testThatOneToOneRelationshipOfHasManyDoesNotHaveIncorrectCollectionIndexMetadataKey
{
    NSDictionary *representation = @{ @"name": @"Blake", @"friend": @{ @"name": @"jeff" } };
    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [userMapping addAttributeMappingsFromDictionary:@{ @"name": @"name", @"@metadata.mapping.collectionIndex": @"luckyNumber" }];
    [userMapping addRelationshipMappingWithSourceKeyPath:@"friend" mapping:userMapping];

    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:representation destinationObject:nil mapping:userMapping];
    mappingOperation.metadata = @{ @"mapping": @{ @"collectionIndex": @25 } };
    mappingOperation.dataSource = dataSource;
    BOOL success = [mappingOperation performMapping:nil];
    expect(success).to.equal(YES);

    RKTestUser *blake = mappingOperation.destinationObject;
    expect(blake).notTo.beNil();
    expect(blake.name).to.equal(@"Blake");
    expect(blake.luckyNumber).to.equal(@25);
    expect(blake.friend).notTo.beNil();
    expect(blake.friend.luckyNumber).to.beNil();
}

@end
