//
//  RKObjectMappingNextGenTest.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 <OCMock/OCMock.h>
#import <OCMock/NSNotificationCenter+OCMAdditions.h>
#import "RKTestEnvironment.h"
#import "RKObjectMapping.h"
#import "RKMappingOperation.h"
#import "RKAttributeMapping.h"
#import "RKRelationshipMapping.h"
#import "RKLog.h"
#import "RKMapperOperation.h"
#import "RKMapperOperation_Private.h"
#import "RKMappingErrors.h"
#import "RKDynamicMappingModels.h"
#import "RKTestAddress.h"
#import "RKTestUser.h"
#import "RKObjectMappingOperationDataSource.h"
#import "RKManagedObjectMappingOperationDataSource.h"
#import "RKDynamicMapping.h"
#import "RKMIMETypeSerialization.h"

// Managed Object Serialization Testific
#import "RKHuman.h"
#import "RKCat.h"

@interface RKObjectMapping ()
+ (void)resetDefaultDateFormatters;
@end

@interface RKExampleGroupWithUserArray : NSObject {
    NSString *_name;
    NSArray *_users;
}

@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSArray *users;

@end

@implementation RKExampleGroupWithUserArray

@synthesize name = _name;
@synthesize users = _users;

+ (RKExampleGroupWithUserArray *)group
{
    return [self new];
}

// isEqual: is consulted by the mapping operation
// to determine if assocation values should be set
- (BOOL)isEqual:(id)object
{
    if ([object isKindOfClass:[RKExampleGroupWithUserArray class]]) {
        return [[(RKExampleGroupWithUserArray *)object name] isEqualToString:self.name];
    } else {
        return NO;
    }
}

@end

@interface RKExampleGroupWithUserSet : NSObject {
    NSString *_name;
    NSSet *_users;
}

@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSSet *users;

@end

@implementation RKExampleGroupWithUserSet

@synthesize name = _name;
@synthesize users = _users;

+ (RKExampleGroupWithUserSet *)group
{
    return [self new];
}

// isEqual: is consulted by the mapping operation
// to determine if assocation values should be set
- (BOOL)isEqual:(id)object
{
    if ([object isKindOfClass:[RKExampleGroupWithUserSet class]]) {
        return [[(RKExampleGroupWithUserSet *)object name] isEqualToString:self.name];
    } else {
        return NO;
    }
}

@end

////////////////////////////////////////////////////////////////////////////////

#pragma mark -

@interface RKObjectMappingNextGenTest : RKTestCase {

}

@end

@implementation RKObjectMappingNextGenTest

#pragma mark - RKObjectKeyPathMapping Tests

- (void)testShouldDefineElementToPropertyMapping
{
    RKAttributeMapping *elementMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"userID"];
    assertThat(elementMapping.sourceKeyPath, is(equalTo(@"id")));
    assertThat(elementMapping.destinationKeyPath, is(equalTo(@"userID")));
}

- (void)testShouldDescribeElementMappings
{
    RKAttributeMapping *elementMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"userID"];
    assertThatBool([[elementMapping description] hasSuffix:@"id => userID>"], is(equalToBool(YES)));
}

#pragma mark - RKObjectMapping Tests

- (void)testShouldDefineMappingFromAnElementToAProperty
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *idMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"userID"];
    [mapping addPropertyMapping:idMapping];
    assertThat(mapping.propertyMappingsBySourceKeyPath[@"id"], is(sameInstance(idMapping)));
}

- (void)testShouldAddMappingsToAttributeMappings
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *idMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"userID"];
    [mapping addPropertyMapping:idMapping];
    assertThatBool([mapping.propertyMappings containsObject:idMapping], is(equalToBool(YES)));
    assertThatBool([mapping.attributeMappings containsObject:idMapping], is(equalToBool(YES)));
}

- (void)testShouldAddMappingsToRelationshipMappings
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKRelationshipMapping *idMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:@"id" toKeyPath:@"userID" withMapping:nil];
    [mapping addPropertyMapping:idMapping];
    assertThatBool([mapping.propertyMappings containsObject:idMapping], is(equalToBool(YES)));
    assertThatBool([mapping.relationshipMappings containsObject:idMapping], is(equalToBool(YES)));
}

- (void)testShouldGenerateAttributeMappings
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    assertThat(mapping.propertyMappingsBySourceKeyPath[@"name"], is(nilValue()));
    [mapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"]];
    assertThat(mapping.propertyMappingsBySourceKeyPath[@"name"], isNot(nilValue()));
}

- (void)testShouldGenerateRelationshipMappings
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKObjectMapping *anotherMapping = [RKObjectMapping mappingForClass:[NSDictionary class]];
    assertThat(mapping.propertyMappingsBySourceKeyPath[@"another"], is(nilValue()));
    [mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"another" toKeyPath:@"another" withMapping:anotherMapping]];
    assertThat(mapping.propertyMappingsBySourceKeyPath[@"another"], isNot(nilValue()));
}

- (void)testShouldGenerateAnInverseMappings
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [mapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"first_name" toKeyPath:@"firstName"]];
    [mapping addAttributeMappingsFromArray:@[@"city", @"state", @"zip"]];
    RKObjectMapping *otherMapping = [RKObjectMapping mappingForClass:[RKTestAddress class]];
    [otherMapping addAttributeMappingsFromArray:@[@"street"]];
    [mapping addRelationshipMappingWithSourceKeyPath:@"address" mapping:otherMapping];
    RKObjectMapping *inverse = [mapping inverseMapping];
    assertThat(inverse.objectClass, is(equalTo([NSMutableDictionary class])));
    assertThat([inverse propertyMappingsBySourceKeyPath][@"firstName"], isNot(nilValue()));
}

- (void)testShouldLetYouRetrieveMappingsByAttribute
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *attributeMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"nameAttribute"];
    [mapping addPropertyMapping:attributeMapping];
    assertThat(mapping.propertyMappingsByDestinationKeyPath[@"nameAttribute"], is(equalTo(attributeMapping)));
}

- (void)testShouldLetYouRetrieveMappingsByRelationship
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKRelationshipMapping *relationshipMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:@"friend" toKeyPath:@"friendRelationship" withMapping:mapping];
    [mapping addPropertyMapping:relationshipMapping];
    assertThat(mapping.propertyMappingsByDestinationKeyPath[@"friendRelationship"], is(equalTo(relationshipMapping)));
}

#pragma mark - RKMapperOperation Tests
// TODO: Move these into RKMapperOperationTest.m

- (void)testShouldPerformBasicMapping
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *idMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"userID"];
    [mapping addPropertyMapping:idMapping];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [mapping addPropertyMapping:nameMapping];

    RKMapperOperation *mapper = [RKMapperOperation new];
    mapper.mappingOperationDataSource = [RKObjectMappingOperationDataSource new];
    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKTestUser *user = [RKTestUser user];
    BOOL success = [mapper mapRepresentation:userInfo toObject:user atKeyPath:@"" usingMapping:mapping metadata:nil];
    assertThatBool(success, is(equalToBool(YES)));
    assertThatInt([user.userID intValue], is(equalToInt(31337)));
    assertThat(user.name, is(equalTo(@"Blake Watters")));
}

- (void)testShouldMapACollectionOfSimpleObjectDictionaries
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *idMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"userID"];
    [mapping addPropertyMapping:idMapping];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [mapping addPropertyMapping:nameMapping];

    RKMapperOperation *mapper = [RKMapperOperation new];
    mapper.mappingOperationDataSource = [RKObjectMappingOperationDataSource new];
    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"users.json"];
    NSArray *users = [mapper mapRepresentations:userInfo atKeyPath:@"" usingMapping:mapping];
    assertThatUnsignedInteger([users count], is(equalToInt(3)));
    RKTestUser *blake = [users objectAtIndex:0];
    assertThat(blake.name, is(equalTo(@"Blake Watters")));
}

- (void)testShouldDetermineTheObjectMappingByConsultingTheMappingProviderWhenThereIsATargetObject
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    NSMutableDictionary *mappingsDictionary = [NSMutableDictionary dictionary];
    [mappingsDictionary setObject:mapping forKey:[NSNull null]];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:mappingsDictionary];
    mapper.targetObject = [RKTestUser user];
    [mapper start];
}

- (void)testShouldAddAnErrorWhenTheKeyPathMappingAndObjectClassDoNotAgree
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:@{[NSNull null] : mapping}];
    NSDictionary *dictionary = [NSDictionary new];
    mapper.targetObject = dictionary;
    [mapper start];
    assertThat(mapper.error, is(notNilValue()));
    // TODO: Better check on the error type...
}

- (void)testShouldMapToATargetObject
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *idMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"userID"];
    [mapping addPropertyMapping:idMapping];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [mapping addPropertyMapping:nameMapping];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:@{[NSNull null] : mapping}];
    RKTestUser *user = [RKTestUser user];
    mapper.targetObject = user;
    [mapper start];
    RKMappingResult *result = mapper.mappingResult;

    assertThat(result, isNot(nilValue()));
    assertThatBool([result firstObject] == user, is(equalToBool(YES)));
    assertThat(user.name, is(equalTo(@"Blake Watters")));
}

- (void)testShouldCreateANewInstanceOfTheAppropriateDestinationObjectWhenThereIsNoTargetObject
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [mapping addPropertyMapping:nameMapping];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:@{ [NSNull null]: mapping }];
    [mapper start];
    id mappingResult = [mapper.mappingResult firstObject];
    assertThatBool([mappingResult isKindOfClass:[RKTestUser class]], is(equalToBool(YES)));
}

- (void)testShouldMapWithoutATargetMapping
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *idMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"userID"];
    [mapping addPropertyMapping:idMapping];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [mapping addPropertyMapping:nameMapping];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:@{ [NSNull null]: mapping }];
    [mapper start];
    RKTestUser *user = [mapper.mappingResult firstObject];
    assertThat(user, is(notNilValue()));
    assertThatBool([user isKindOfClass:[RKTestUser class]], is(equalToBool(YES)));
    assertThat(user.name, is(equalTo(@"Blake Watters")));
}

- (void)testShouldMapACollectionOfObjects
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *idMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"userID"];
    [mapping addPropertyMapping:idMapping];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [mapping addPropertyMapping:nameMapping];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"users.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:@{ [NSNull null]: mapping }];
    [mapper start];
    NSArray *users = [mapper.mappingResult array];
    assertThatBool([users isKindOfClass:[NSArray class]], is(equalToBool(YES)));
    assertThatUnsignedInteger([users count], is(equalToInt(3)));
    RKTestUser *user = [users objectAtIndex:0];
    assertThatBool([user isKindOfClass:[RKTestUser class]], is(equalToBool(YES)));
    assertThat(user.name, is(equalTo(@"Blake Watters")));
}

- (void)testShouldMapACollectionOfObjectsWithDynamicKeys
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    mapping.forceCollectionMapping = YES;
    [mapping addAttributeMappingFromKeyOfRepresentationToAttribute:@"name"];
    RKAttributeMapping *idMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"(name).id" toKeyPath:@"userID"];
    [mapping addPropertyMapping:idMapping];
    NSMutableDictionary *mappingsDictionary = [NSMutableDictionary dictionary];
    [mappingsDictionary setObject:mapping forKey:@"users"];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"DynamicKeys.json"];
    
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:mappingsDictionary];
    [mapper start];
    NSArray *users = [mapper.mappingResult array];
    assertThatBool([users isKindOfClass:[NSArray class]], is(equalToBool(YES)));
    assertThatUnsignedInteger([users count], is(equalToInt(2)));
    RKTestUser *user = [users objectAtIndex:0];
    assertThatBool([user isKindOfClass:[RKTestUser class]], is(equalToBool(YES)));
    assertThat(user.name, is(equalTo(@"blake")));
    user = [users objectAtIndex:1];
    assertThatBool([user isKindOfClass:[RKTestUser class]], is(equalToBool(YES)));
    assertThat(user.name, is(equalTo(@"rachit")));
}

- (void)testShouldMapACollectionOfObjectsWithDynamicKeysAndRelationships
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    mapping.forceCollectionMapping = YES;
    [mapping addAttributeMappingFromKeyOfRepresentationToAttribute:@"name"];

    RKObjectMapping *addressMapping = [RKObjectMapping mappingForClass:[RKTestAddress class]];
    [addressMapping addAttributeMappingsFromArray:@[@"city", @"state"]];
    [mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"(name).address" toKeyPath:@"address" withMapping:addressMapping]];;
    NSMutableDictionary *mappingsDictionary = [NSMutableDictionary dictionary];
    [mappingsDictionary setObject:mapping forKey:@"users"];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"DynamicKeysWithRelationship.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:mappingsDictionary];
    [mapper start];
    RKMappingResult *result = mapper.mappingResult;
    NSArray *users = [result array];
    assertThatBool([users isKindOfClass:[NSArray class]], is(equalToBool(YES)));
    assertThatUnsignedInteger([users count], is(equalToInt(2)));
    RKTestUser *user = [users objectAtIndex:0];
    assertThatBool([user isKindOfClass:[RKTestUser class]], is(equalToBool(YES)));
    assertThat(user.name, is(equalTo(@"blake")));
    user = [users objectAtIndex:1];
    assertThatBool([user isKindOfClass:[RKTestUser class]], is(equalToBool(YES)));
    assertThat(user.name, is(equalTo(@"rachit")));
    assertThat(user.address, isNot(nilValue()));
    assertThat(user.address.city, is(equalTo(@"New York")));
}

- (void)testShouldMapANestedArrayOfObjectsWithDynamicKeysAndArrayRelationships
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKExampleGroupWithUserArray class]];
    [mapping addAttributeMappingsFromArray:@[@"name"]];


    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    userMapping.forceCollectionMapping = YES;
    [userMapping addAttributeMappingFromKeyOfRepresentationToAttribute:@"name"];
    [mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"users" toKeyPath:@"users" withMapping:userMapping]];;

    RKObjectMapping *addressMapping = [RKObjectMapping mappingForClass:[RKTestAddress class]];
    [addressMapping addAttributeMappingsFromDictionary:@{
        @"city": @"city",
        @"state": @"state",
        @"country": @"country",
     }];
    [userMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"(name).address" toKeyPath:@"address" withMapping:addressMapping]];;
    NSMutableDictionary *mappingsDictionary = [NSMutableDictionary dictionary];
    [mappingsDictionary setObject:mapping forKey:@"groups"];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"DynamicKeysWithNestedRelationship.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:mappingsDictionary];
    [mapper start];
    RKMappingResult *result = mapper.mappingResult;

    NSArray *groups = [result array];
    assertThatBool([groups isKindOfClass:[NSArray class]], is(equalToBool(YES)));
    assertThatUnsignedInteger([groups count], is(equalToInt(2)));

    RKExampleGroupWithUserArray *group = [groups objectAtIndex:0];
    assertThatBool([group isKindOfClass:[RKExampleGroupWithUserArray class]], is(equalToBool(YES)));
    assertThat(group.name, is(equalTo(@"restkit")));
    NSArray *users = group.users;
    RKTestUser *user = [users objectAtIndex:0];
    assertThatBool([user isKindOfClass:[RKTestUser class]], is(equalToBool(YES)));
    assertThat(user.name, is(equalTo(@"blake")));
    user = [users objectAtIndex:1];
    assertThatBool([user isKindOfClass:[RKTestUser class]], is(equalToBool(YES)));
    assertThat(user.name, is(equalTo(@"rachit")));
    assertThat(user.address, isNot(nilValue()));
    assertThat(user.address.city, is(equalTo(@"New York")));

    group = [groups objectAtIndex:1];
    assertThatBool([group isKindOfClass:[RKExampleGroupWithUserArray class]], is(equalToBool(YES)));
    assertThat(group.name, is(equalTo(@"others")));
    users = group.users;
    assertThatUnsignedInteger([users count], is(equalToInt(1)));
    user = [users objectAtIndex:0];
    assertThatBool([user isKindOfClass:[RKTestUser class]], is(equalToBool(YES)));
    assertThat(user.name, is(equalTo(@"bjorn")));
    assertThat(user.address, isNot(nilValue()));
    assertThat(user.address.city, is(equalTo(@"Gothenburg")));
    assertThat(user.address.country, is(equalTo(@"Sweden")));
}

- (void)testShouldMapANestedArrayOfObjectsWithDynamicKeysAndSetRelationships
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKExampleGroupWithUserSet class]];
    [mapping addAttributeMappingsFromArray:@[@"name"]];


    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    userMapping.forceCollectionMapping = YES;
    [userMapping addAttributeMappingFromKeyOfRepresentationToAttribute:@"name"];
    [mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"users" toKeyPath:@"users" withMapping:userMapping]];;

    RKObjectMapping *addressMapping = [RKObjectMapping mappingForClass:[RKTestAddress class]];
    [addressMapping  addAttributeMappingsFromDictionary:@{
        @"city": @"city",
        @"state": @"state",
        @"country": @"country",
     }];
    [userMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"(name).address" toKeyPath:@"address" withMapping:addressMapping]];;
    NSMutableDictionary *mappingsDictionary = [NSMutableDictionary dictionary];
    [mappingsDictionary setObject:mapping forKey:@"groups"];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"DynamicKeysWithNestedRelationship.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:mappingsDictionary];
    [mapper start];
    RKMappingResult *result = mapper.mappingResult;

    NSArray *groups = [result array];
    assertThatBool([groups isKindOfClass:[NSArray class]], is(equalToBool(YES)));
    assertThatUnsignedInteger([groups count], is(equalToInt(2)));

    RKExampleGroupWithUserSet *group = [groups objectAtIndex:0];
    assertThatBool([group isKindOfClass:[RKExampleGroupWithUserSet class]], is(equalToBool(YES)));
    assertThat(group.name, is(equalTo(@"restkit")));


    NSSortDescriptor *sortByName = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
    NSArray *descriptors = [NSArray arrayWithObject:sortByName];;
    NSArray *users = [group.users sortedArrayUsingDescriptors:descriptors];
    RKTestUser *user = [users objectAtIndex:0];
    assertThatBool([user isKindOfClass:[RKTestUser class]], is(equalToBool(YES)));
    assertThat(user.name, is(equalTo(@"blake")));
    user = [users objectAtIndex:1];
    assertThatBool([user isKindOfClass:[RKTestUser class]], is(equalToBool(YES)));
    assertThat(user.name, is(equalTo(@"rachit")));
    assertThat(user.address, isNot(nilValue()));
    assertThat(user.address.city, is(equalTo(@"New York")));

    group = [groups objectAtIndex:1];
    assertThatBool([group isKindOfClass:[RKExampleGroupWithUserSet class]], is(equalToBool(YES)));
    assertThat(group.name, is(equalTo(@"others")));
    users = [group.users sortedArrayUsingDescriptors:descriptors];
    assertThatUnsignedInteger([users count], is(equalToInt(1)));
    user = [users objectAtIndex:0];
    assertThatBool([user isKindOfClass:[RKTestUser class]], is(equalToBool(YES)));
    assertThat(user.name, is(equalTo(@"bjorn")));
    assertThat(user.address, isNot(nilValue()));
    assertThat(user.address.city, is(equalTo(@"Gothenburg")));
    assertThat(user.address.country, is(equalTo(@"Sweden")));
}


- (void)testShouldBeAbleToMapFromAUserObjectToADictionary
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
    RKAttributeMapping *idMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"userID" toKeyPath:@"id"];
    [mapping addPropertyMapping:idMapping];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [mapping addPropertyMapping:nameMapping];

    RKTestUser *user = [RKTestUser user];
    user.name = @"Blake Watters";
    user.userID = [NSNumber numberWithInt:123];

    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:user mappingsDictionary:@{ [NSNull null]: mapping }];
    [mapper start];
    RKMappingResult *result = mapper.mappingResult;
    NSDictionary *userInfo = [result firstObject];
    assertThatBool([userInfo isKindOfClass:[NSDictionary class]], is(equalToBool(YES)));
    assertThat([userInfo valueForKey:@"name"], is(equalTo(@"Blake Watters")));
}

- (void)testShouldMapRegisteredSubKeyPathsOfAnUnmappableDictionaryAndReturnTheResults
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *idMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"userID"];
    [mapping addPropertyMapping:idMapping];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [mapping addPropertyMapping:nameMapping];
    NSMutableDictionary *mappingsDictionary = [NSMutableDictionary dictionary];
    [mappingsDictionary setObject:mapping forKey:@"user"];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"nested_user.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:mappingsDictionary];
    [mapper start];
    NSDictionary *dictionary = [mapper.mappingResult dictionary];
    assertThatBool([dictionary isKindOfClass:[NSDictionary class]], is(equalToBool(YES)));
    RKTestUser *user = [dictionary objectForKey:@"user"];
    assertThat(user, isNot(nilValue()));
    assertThat(user.name, is(equalTo(@"Blake Watters")));
}

#pragma mark Mapping Error States

- (void)testShouldAddAnErrorWhenYouTryToMapAnArrayToATargetObject
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *idMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"userID"];
    [mapping addPropertyMapping:idMapping];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [mapping addPropertyMapping:nameMapping];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"users.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:@{ [NSNull null]: mapping }];
    mapper.targetObject = [RKTestUser user];
    [mapper start];
    assertThatInteger(mapper.error.code, is(equalToInt(RKMappingErrorTypeMismatch)));
}

- (void)testShouldAddAnErrorWhenAttemptingToMapADictionaryWithoutAnObjectMapping
{
    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:@{}];
    [mapper start];
    assertThat([mapper.error localizedDescription], is(equalTo(@"No mappable object representations were found at the key paths searched.")));
}

- (void)testShouldAddAnErrorWhenAttemptingToMapACollectionWithoutAnObjectMapping
{
    NSMutableDictionary *mappingsDictionary = [NSMutableDictionary dictionary];
    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"users.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:mappingsDictionary];
    [mapper start];
    assertThat([mapper.error localizedDescription], is(equalTo(@"No mappable object representations were found at the key paths searched.")));
}

- (void)testThatAnErrorIsSetWithAHelpfulDescriptionWhenNoKeyPathsMatchTheArrayOfObjectsRepresentationBeingMapped
{
    RKObjectMapping *mapping1 = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
    RKObjectMapping *mapping2 = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
    NSDictionary *mappingsDictionary = @{ @"this": mapping1, @"that": mapping2 };
    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"users.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:mappingsDictionary];
    [mapper start];
    assertThat([mapper.error localizedDescription], is(equalTo(@"No mappable object representations were found at the key paths searched.")));
    assertThat([mapper.error localizedFailureReason], is(equalTo(@"The mapping operation was unable to find any nested object representations at the key paths searched: that, this\nThis likely indicates that you have misconfigured the key paths for your mappings.")));
}

- (void)testThatAnErrorIsSetWithAHelpfulDescriptionWhenNoKeyPathsMatchTheObjectRepresentationBeingMapped
{
    RKObjectMapping *mapping1 = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
    RKObjectMapping *mapping2 = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
    NSDictionary *mappingsDictionary = @{ @"this": mapping1, @"that": mapping2 };
    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"RailsUser.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:mappingsDictionary];
    [mapper start];
    assertThat([mapper.error localizedDescription], is(equalTo(@"No mappable object representations were found at the key paths searched.")));
    assertThat([mapper.error localizedFailureReason], is(equalTo(@"The mapping operation was unable to find any nested object representations at the key paths searched: that, this\nThe representation inputted to the mapper was found to contain nested object representations at the following key paths: user\nThis likely indicates that you have misconfigured the key paths for your mappings.")));
}

#pragma mark RKMapperOperationDelegate Tests

- (void)testShouldInformTheDelegateWhenMappingBegins
{
    id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(RKMapperOperationDelegate)];
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"users.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:@{ [NSNull null]: mapping }];
    [[mockDelegate expect] mapperWillStartMapping:mapper];
    mapper.delegate = mockDelegate;
    [mapper start];
    [mockDelegate verify];
}

- (void)testShouldInformTheDelegateWhenMappingEnds
{
    id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(RKMapperOperationDelegate)];
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"users.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:@{ [NSNull null]: mapping }];
    [[mockDelegate stub] mapperWillStartMapping:mapper];
    [[mockDelegate expect] mapperDidFinishMapping:mapper];
    mapper.delegate = mockDelegate;
    [mapper start];
    [mockDelegate verify];
}

- (void)testShouldInformTheDelegateWhenCheckingForObjectMappingForKeyPathIsSuccessful
{
    id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(RKMapperOperationDelegate)];
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:@{ [NSNull null]: mapping }];
    [[mockDelegate expect] mapper:mapper didFindRepresentationOrArrayOfRepresentations:OCMOCK_ANY atKeyPath:nil];
    mapper.delegate = mockDelegate;
    [mapper start];
    [mockDelegate verify];
}

- (void)testShouldInformTheDelegateWhenCheckingForObjectMappingForKeyPathIsNotSuccessful
{
    NSMutableDictionary *mappingsDictionary = [NSMutableDictionary dictionary];
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [mappingsDictionary setObject:mapping forKey:@"users"];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:mappingsDictionary];
    id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(RKMapperOperationDelegate)];
    [[mockDelegate expect] mapper:mapper didNotFindRepresentationOrArrayOfRepresentationsAtKeyPath:@"users"];
    mapper.delegate = mockDelegate;
    [mapper start];
    [mockDelegate verify];
}

- (void)testShouldNotifyTheDelegateWhenItDidMapAnObject
{
    id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(RKMapperOperationDelegate)];
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [mapping addPropertyMapping:nameMapping];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:@{ [NSNull null]: mapping }];
    [[mockDelegate expect] mapper:mapper didFinishMappingOperation:OCMOCK_ANY forKeyPath:nil];
    mapper.delegate = mockDelegate;
    [mapper start];
    [mockDelegate verify];
}

- (void)testMappingConstructsMappingInfoDictionaryWithAttributeInfo
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [mapping addAttributeMappingsFromArray:@[ @"name" ]];
    NSDictionary *representation = @{ @"name": @"Blake Watters" };
    RKTestUser *user = [RKTestUser new];
    RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:representation destinationObject:user mapping:mapping];
    [mappingOperation start];
    expect(mappingOperation.mappingInfo).notTo.beNil();
    RKPropertyMapping *nameMapping = mappingOperation.mappingInfo[@"name"];
    expect(nameMapping).to.equal([mapping propertyMappingsByDestinationKeyPath][@"name"]);
}

- (void)testMappingConstructsMappingInfoDictionaryWithMergedRelationshipInfo
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [mapping addAttributeMappingsFromArray:@[ @"name" ]];
    [mapping addRelationshipMappingWithSourceKeyPath:@"friends" mapping:mapping];
    NSDictionary *representation = @{ @"name": @"Blake Watters", @"friends": @[ @{ @"name": @"Jeff Arena"} ] };
    RKTestUser *user = [RKTestUser new];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:representation destinationObject:user mapping:mapping];
    mappingOperation.dataSource = dataSource;
    [mappingOperation start];
    expect(mappingOperation.mappingInfo).notTo.beNil();
    RKPropertyMapping *friendsMapping = mappingOperation.mappingInfo[@"friends"];
    RKPropertyMapping *nameMapping = mappingOperation.mappingInfo[@"name"];
    RKPropertyMapping *nestedNameMapping = mappingOperation.mappingInfo[@"friends.name"];
    expect(nameMapping).to.equal([mapping propertyMappingsByDestinationKeyPath][@"name"]);
    expect(friendsMapping).to.equal([mapping propertyMappingsByDestinationKeyPath][@"friends"]);
    expect(nestedNameMapping).to.equal([mapping propertyMappingsByDestinationKeyPath][@"name"]);
}

- (void)testThatMappingHasManyDoesNotDuplicateRelationshipMappingInMappingInfo
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [mapping addAttributeMappingsFromArray:@[ @"name" ]];
    [mapping addRelationshipMappingWithSourceKeyPath:@"friends" mapping:mapping];
    NSDictionary *representation = @{ @"name": @"Blake Watters", @"friends": @[ @{ @"name": @"Jeff Arena"}, @{ @"name": @"Dan Gellert" } ] };
    RKTestUser *user = [RKTestUser new];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:representation destinationObject:user mapping:mapping];
    mappingOperation.dataSource = dataSource;
    [mappingOperation start];
    expect(mappingOperation.mappingInfo).notTo.beNil();
    RKPropertyMapping *friendsMapping = mappingOperation.mappingInfo[@"friends"];
    expect(friendsMapping).to.equal([mapping propertyMappingsByDestinationKeyPath][@"friends"]);
}

- (void)testThatDynamicMappingCoalescesObjectClassesInMappingInfo
{
    // use a dynamic mapping with 2 classes, should have an objectClass as an array containing both
}

- (void)testMapperOperationAggregatesMappingInfoFromChildOperations
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [mapping addAttributeMappingsFromArray:@[ @"name" ]];
    [mapping addRelationshipMappingWithSourceKeyPath:@"friends" mapping:mapping];
    NSDictionary *representation = @{ @"name": @"Blake Watters", @"friends": @[ @{ @"name": @"Jeff Arena"}, @{ @"name": @"Dan Gellert" } ] };
    RKTestUser *user = [RKTestUser new];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    RKMapperOperation *mapperOperation = [[RKMapperOperation alloc] initWithRepresentation:representation mappingsDictionary:@{ [NSNull null]: mapping }];
    mapperOperation.targetObject = user;
    mapperOperation.mappingOperationDataSource = dataSource;
    [mapperOperation start];
    expect(mapperOperation.mappingInfo).notTo.beNil();
    RKPropertyMapping *friendsMapping = mapperOperation.mappingInfo[[NSNull null]][@"friends"];
    expect(friendsMapping).to.equal([mapping propertyMappingsByDestinationKeyPath][@"friends"]);
}

- (BOOL)fakeValidateValue:(inout id *)ioValue forKeyPath:(NSString *)inKey error:(out NSError **)outError
{
    *outError = [NSError errorWithDomain:RKErrorDomain code:1234 userInfo:nil];
    return NO;
}

- (void)testShouldNotifyTheDelegateWhenItFailedToMapAnObject
{
    id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(RKMapperOperationDelegate)];
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:NSClassFromString(@"OCPartialMockObject")];
    [mapping addAttributeMappingsFromArray:@[@"name"]];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:@{ [NSNull null]: mapping }];
    RKTestUser *exampleUser = [RKTestUser new];
    id mockObject = [OCMockObject partialMockForObject:exampleUser];
    [[[mockObject expect] andCall:@selector(fakeValidateValue:forKeyPath:error:) onObject:self] validateValue:(id __autoreleasing *)[OCMArg anyPointer] forKeyPath:OCMOCK_ANY error:(NSError * __autoreleasing *)[OCMArg anyPointer]];
    mapper.targetObject = mockObject;
    [[mockDelegate expect] mapper:mapper didFailMappingOperation:OCMOCK_ANY forKeyPath:nil withError:OCMOCK_ANY];
    mapper.delegate = mockDelegate;
    [mapper start];
    [mockObject verify];
    [mockDelegate verify];
}

#pragma mark - RKObjectMappingOperationTests

- (void)testShouldBeAbleToMapADictionaryToAUser
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
    RKAttributeMapping *idMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"userID"];
    [mapping addPropertyMapping:idMapping];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [mapping addPropertyMapping:nameMapping];

    NSMutableDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:123], @"id", @"Blake Watters", @"name", nil];
    RKTestUser *user = [RKTestUser user];

    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    [operation start];
    assertThat(user.name, is(equalTo(@"Blake Watters")));
    assertThatInt([user.userID intValue], is(equalToInt(123)));
}

- (void)testShouldConsiderADictionaryContainingOnlyNullValuesForKeysMappable
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
    RKAttributeMapping *idMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"userID"];
    [mapping addPropertyMapping:idMapping];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [mapping addPropertyMapping:nameMapping];

    NSDictionary *dictionary = @{ @"name": [NSNull null] };
    RKTestUser *user = [RKTestUser user];

    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    BOOL success = [operation performMapping:nil];
    assertThatBool(success, is(equalToBool(YES)));
    assertThat(user.name, is(nilValue()));
}

- (void)testShouldBeAbleToMapAUserToADictionary
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
    RKAttributeMapping *idMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"userID" toKeyPath:@"id"];
    [mapping addPropertyMapping:idMapping];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [mapping addPropertyMapping:nameMapping];

    RKTestUser *user = [RKTestUser user];
    user.name = @"Blake Watters";
    user.userID = [NSNumber numberWithInt:123];

    NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:user destinationObject:dictionary mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    BOOL success = [operation performMapping:nil];
    assertThatBool(success, is(equalToBool(YES)));
    assertThat([dictionary valueForKey:@"name"], is(equalTo(@"Blake Watters")));
    assertThatInt([[dictionary valueForKey:@"id"] intValue], is(equalToInt(123)));
}

- (void)testShouldReturnNoWithoutErrorWhenGivenASourceObjectThatContainsNoMappableKeys
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
    RKAttributeMapping *idMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"userID"];
    [mapping addPropertyMapping:idMapping];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [mapping addPropertyMapping:nameMapping];

    NSMutableDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:@"blue", @"favorite_color", @"coffee", @"preferred_beverage", nil];
    RKTestUser *user = [RKTestUser user];

    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    BOOL success = [operation performMapping:&error];
    assertThatBool(success, is(equalToBool(NO)));
    assertThat(error, is(notNilValue()));
    assertThatInteger(operation.error.code, is(equalToInteger(RKMappingErrorUnmappableRepresentation)));
}

- (void)testShouldInformTheDelegateOfAnErrorWhenMappingFailsBecauseThereIsNoMappableContent
{
    id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(RKMappingOperationDelegate)];
    
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
    RKAttributeMapping *idMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"userID"];
    [mapping addPropertyMapping:idMapping];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [mapping addPropertyMapping:nameMapping];

    NSMutableDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:@"blue", @"favorite_color", @"coffee", @"preferred_beverage", nil];
    RKTestUser *user = [RKTestUser user];

    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    operation.delegate = mockDelegate;
    BOOL success = [operation performMapping:nil];
    assertThatBool(success, is(equalToBool(NO)));
    [mockDelegate verify];
}

- (void)testShouldSetTheErrorWhenMappingOperationFails
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
    RKAttributeMapping *idMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"userID"];
    [mapping addPropertyMapping:idMapping];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [mapping addPropertyMapping:nameMapping];

    NSMutableDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:@"FAILURE", @"id", nil];
    RKTestUser *user = [RKTestUser user];
    id mockObject = [OCMockObject partialMockForObject:user];
    [[[mockObject expect] andCall:@selector(fakeValidateValue:forKeyPath:error:) onObject:self] validateValue:(id __autoreleasing *)[OCMArg anyPointer] forKeyPath:OCMOCK_ANY error:(NSError * __autoreleasing *)[OCMArg anyPointer]];

    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:mockObject mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];
    assertThat(error, isNot(nilValue()));
}

#pragma mark - Attribute Mapping

- (void)testShouldMapAStringToADateAttribute
{
    [RKObjectMapping resetDefaultDateFormatters];

    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *birthDateMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"birthdate" toKeyPath:@"birthDate"];
    [mapping addPropertyMapping:birthDateMapping];

    NSDictionary *dictionary = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKTestUser *user = [RKTestUser user];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];

    NSDateFormatter *dateFormatter = [NSDateFormatter new];
    dateFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"];
    [dateFormatter setDateFormat:@"MM/dd/yyyy"];
    assertThat([dateFormatter stringFromDate:user.birthDate], is(equalTo(@"11/27/1982")));
}

- (void)testShouldMapStringToURL
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *websiteMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"website" toKeyPath:@"website"];
    [mapping addPropertyMapping:websiteMapping];

    NSDictionary *dictionary = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKTestUser *user = [RKTestUser user];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];

    assertThat(user.website, isNot(nilValue()));
    assertThatBool([user.website isKindOfClass:[NSURL class]], is(equalToBool(YES)));
    assertThat([user.website absoluteString], is(equalTo(@"http://restkit.org/")));
}

- (void)testShouldMapAStringToANumberBool
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *websiteMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"is_developer" toKeyPath:@"isDeveloper"];
    [mapping addPropertyMapping:websiteMapping];

    NSDictionary *dictionary = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKTestUser *user = [RKTestUser user];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];

    assertThatBool([[user isDeveloper] boolValue], is(equalToBool(YES)));
}

- (void)testShouldMapAShortTrueStringToANumberBool
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *websiteMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"is_developer" toKeyPath:@"isDeveloper"];
    [mapping addPropertyMapping:websiteMapping];

    NSDictionary *dictionary = [[RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"] mutableCopy];
    RKTestUser *user = [RKTestUser user];
    [dictionary setValue:@"T" forKey:@"is_developer"];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];

    assertThatBool([[user isDeveloper] boolValue], is(equalToBool(YES)));
}

- (void)testShouldMapAShortFalseStringToANumberBool
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *websiteMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"is_developer" toKeyPath:@"isDeveloper"];
    [mapping addPropertyMapping:websiteMapping];

    NSDictionary *dictionary = [[RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"] mutableCopy];
    RKTestUser *user = [RKTestUser user];
    [dictionary setValue:@"f" forKey:@"is_developer"];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];

    assertThatBool([[user isDeveloper] boolValue], is(equalToBool(NO)));
}

- (void)testShouldMapAYesStringToANumberBool
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *websiteMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"is_developer" toKeyPath:@"isDeveloper"];
    [mapping addPropertyMapping:websiteMapping];

    NSDictionary *dictionary = [[RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"] mutableCopy];
    RKTestUser *user = [RKTestUser user];
    [dictionary setValue:@"yes" forKey:@"is_developer"];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];

    assertThatBool([[user isDeveloper] boolValue], is(equalToBool(YES)));
}

- (void)testShouldMapANoStringToANumberBool
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *websiteMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"is_developer" toKeyPath:@"isDeveloper"];
    [mapping addPropertyMapping:websiteMapping];

    NSDictionary *dictionary = [[RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"] mutableCopy];
    RKTestUser *user = [RKTestUser user];
    [dictionary setValue:@"NO" forKey:@"is_developer"];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];

    assertThatBool([[user isDeveloper] boolValue], is(equalToBool(NO)));
}

- (void)testShouldMapAYCharacterStringToANumberBool
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *websiteMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"is_developer" toKeyPath:@"isDeveloper"];
    [mapping addPropertyMapping:websiteMapping];
    
    NSDictionary *dictionary = [[RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"] mutableCopy];
    RKTestUser *user = [RKTestUser user];
    [dictionary setValue:@"y" forKey:@"is_developer"];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];
    
    assertThatBool([[user isDeveloper] boolValue], is(equalToBool(YES)));
}

- (void)testShouldMapANCharacterStringToANumberBool
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *websiteMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"is_developer" toKeyPath:@"isDeveloper"];
    [mapping addPropertyMapping:websiteMapping];
    
    NSDictionary *dictionary = [[RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"] mutableCopy];
    RKTestUser *user = [RKTestUser user];
    [dictionary setValue:@"n" forKey:@"is_developer"];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];
    
    assertThatBool([[user isDeveloper] boolValue], is(equalToBool(NO)));
}

- (void)testShouldMapAStringToANumber
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *websiteMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"lucky_number" toKeyPath:@"luckyNumber"];
    [mapping addPropertyMapping:websiteMapping];

    NSDictionary *dictionary = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKTestUser *user = [RKTestUser user];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];

    assertThatInt([user.luckyNumber intValue], is(equalToInt(187)));
}

- (void)testShouldMapAStringToADecimalNumber
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *websiteMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"weight" toKeyPath:@"weight"];
    [mapping addPropertyMapping:websiteMapping];

    NSDictionary *dictionary = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKTestUser *user = [RKTestUser user];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];

    NSDecimalNumber *weight = user.weight;
    assertThatBool([weight isKindOfClass:[NSDecimalNumber class]], is(equalToBool(YES)));
    assertThatInteger([weight compare:[NSDecimalNumber decimalNumberWithString:@"131.3"]], is(equalToInt(NSOrderedSame)));
}

- (void)testShouldMapANumberToAString
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *websiteMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"lucky_number" toKeyPath:@"name"];
    [mapping addPropertyMapping:websiteMapping];

    NSDictionary *dictionary = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKTestUser *user = [RKTestUser user];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];

    assertThat(user.name, is(equalTo(@"187")));
}

- (void)testShouldMapANumberToANSDecimalNumber
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *websiteMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"lucky_number" toKeyPath:@"weight"];
    [mapping addPropertyMapping:websiteMapping];

    NSDictionary *dictionary = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKTestUser *user = [RKTestUser user];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];

    NSDecimalNumber *weight = user.weight;
    assertThatBool([weight isKindOfClass:[NSDecimalNumber class]], is(equalToBool(YES)));
    assertThatInteger([weight compare:[NSDecimalNumber decimalNumberWithString:@"187"]], is(equalToInt(NSOrderedSame)));
}

- (void)testShouldMapANumberToADate
{
    NSDateFormatter *dateFormatter = [NSDateFormatter new];
    [dateFormatter setDateFormat:@"MM/dd/yyyy"];
    NSDate *date = [dateFormatter dateFromString:@"11/27/1982"];

    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *birthDateMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"dateAsNumber" toKeyPath:@"birthDate"];
    [mapping addPropertyMapping:birthDateMapping];

    NSMutableDictionary *dictionary = [[RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"] mutableCopy];
    [dictionary setValue:[NSNumber numberWithInt:[date timeIntervalSince1970]] forKey:@"dateAsNumber"];
    RKTestUser *user = [RKTestUser user];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];

    assertThat([dateFormatter stringFromDate:user.birthDate], is(equalTo(@"11/27/1982")));
}

- (void)testShouldMapANestedKeyPathToAnAttribute
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *countryMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"address.country" toKeyPath:@"country"];
    [mapping addPropertyMapping:countryMapping];

    NSDictionary *dictionary = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKTestUser *user = [RKTestUser user];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];

    assertThat(user.country, is(equalTo(@"USA")));
}

- (void)testShouldMapANestedArrayOfStringsToAnAttribute
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *countryMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"interests" toKeyPath:@"interests"];
    [mapping addPropertyMapping:countryMapping];

    NSDictionary *dictionary = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKTestUser *user = [RKTestUser user];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];

    NSArray *interests = [NSArray arrayWithObjects:@"Hacking", @"Running", nil];
    assertThat(user.interests, is(equalTo(interests)));
}

- (void)testShouldMapANestedDictionaryToAnAttribute
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *countryMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"address" toKeyPath:@"addressDictionary"];
    [mapping addPropertyMapping:countryMapping];

    NSDictionary *dictionary = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKTestUser *user = [RKTestUser user];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];

    NSDictionary *address = @{
                             @"city": @"Carrboro",
                             @"state": @"North Carolina",
                             @"id": [NSNumber numberWithInt:1234],
                             @"country": @"USA"};
    assertThat(user.addressDictionary, is(equalTo(address)));
}

- (void)testShouldNotSetAPropertyWhenTheValueIsTheSame
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [mapping addPropertyMapping:nameMapping];

    NSDictionary *dictionary = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKTestUser *user = [RKTestUser user];
    user.name = @"Blake Watters";
    id mockUser = [OCMockObject partialMockForObject:user];
    [(RKTestUser *)[mockUser reject] setName:OCMOCK_ANY];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];
}

- (void)testShouldNotSetTheDestinationPropertyWhenBothAreNil
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [mapping addPropertyMapping:nameMapping];

    NSMutableDictionary *dictionary = [[RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"] mutableCopy];
    [dictionary setValue:[NSNull null] forKey:@"name"];
    RKTestUser *user = [RKTestUser user];
    user.name = nil;
    id mockUser = [OCMockObject partialMockForObject:user];
    [(RKTestUser *)[mockUser reject] setName:OCMOCK_ANY];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];
}

- (void)testShouldSetNilForNSNullValues
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [mapping addPropertyMapping:nameMapping];

    NSDictionary *dictionary = [[RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"] mutableCopy];
    [dictionary setValue:[NSNull null] forKey:@"name"];
    RKTestUser *user = [RKTestUser user];
    user.name = @"Blake Watters";
    id mockUser = [OCMockObject partialMockForObject:user];
    [(RKTestUser *)[mockUser expect] setName:nil];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];
    [mockUser verify];
}

- (void)testDelegateIsInformedWhenANilValueIsMappedForNSNullWithExistingValue
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [mapping addPropertyMapping:nameMapping];

    NSDictionary *dictionary = [[RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"] mutableCopy];
    [dictionary setValue:[NSNull null] forKey:@"name"];
    RKTestUser *user = [RKTestUser user];
    user.name = @"Blake Watters";
    id mockDelegate = [OCMockObject mockForProtocol:@protocol(RKMappingOperationDelegate)];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];    
    operation.delegate = mockDelegate;
    NSError *error = nil;
    [[mockDelegate expect] mappingOperation:operation didFindValue:[NSNull null] forKeyPath:@"name" mapping:nameMapping];
    [[mockDelegate expect] mappingOperation:operation didSetValue:nil forKeyPath:@"name" usingMapping:nameMapping];
    operation.dataSource = dataSource;
    [operation performMapping:&error];
    [mockDelegate verify];
}

- (void)testDelegateIsInformedWhenUnchangedValueIsSkipped
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [mapping addPropertyMapping:nameMapping];

    NSDictionary *dictionary = [[RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"] mutableCopy];
    [dictionary setValue:@"Blake Watters" forKey:@"name"];
    RKTestUser *user = [RKTestUser user];
    user.name = @"Blake Watters";
    id mockDelegate = [OCMockObject mockForProtocol:@protocol(RKMappingOperationDelegate)];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    operation.delegate = mockDelegate;
    NSError *error = nil;
    [[mockDelegate expect] mappingOperation:operation didFindValue:@"Blake Watters" forKeyPath:@"name" mapping:nameMapping];
    [[mockDelegate expect] mappingOperation:operation didNotSetUnchangedValue:@"Blake Watters" forKeyPath:@"name" usingMapping:nameMapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    [operation performMapping:&error];
    [mockDelegate verify];
}

- (void)testShouldOptionallySetDefaultValueForAMissingKeyPath
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [mapping addPropertyMapping:nameMapping];

    NSMutableDictionary *dictionary = [[RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"] mutableCopy];
    [dictionary removeObjectForKey:@"name"];
    RKTestUser *user = [RKTestUser user];
    user.name = @"Blake Watters";
    id mockUser = [OCMockObject partialMockForObject:user];
    [(RKTestUser *)[mockUser expect] setName:nil];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    id mockMapping = [OCMockObject partialMockForObject:mapping];
    BOOL returnValue = YES;
    [[[mockMapping expect] andReturnValue:OCMOCK_VALUE(returnValue)] shouldSetDefaultValueForMissingAttributes];
    NSError *error = nil;
    [operation performMapping:&error];
    [mockUser verify];
}

- (void)testShouldOptionallyIgnoreAMissingSourceKeyPath
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [mapping addPropertyMapping:nameMapping];

    NSMutableDictionary *dictionary = [[RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"] mutableCopy];
    [dictionary removeObjectForKey:@"name"];
    RKTestUser *user = [RKTestUser user];
    user.name = @"Blake Watters";
    id mockUser = [OCMockObject partialMockForObject:user];
    [(RKTestUser *)[mockUser reject] setName:nil];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    id mockMapping = [OCMockObject partialMockForObject:mapping];
    BOOL returnValue = NO;
    [[[mockMapping expect] andReturnValue:OCMOCK_VALUE(returnValue)] shouldSetDefaultValueForMissingAttributes];
    NSError *error = nil;
    [operation performMapping:&error];
    assertThat(user.name, is(equalTo(@"Blake Watters")));
}

- (void)testMappingToAnNSDataAttributeUsingKeyedArchiver
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *attributeMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"arrayOfStrings" toKeyPath:@"data"];
    [mapping addPropertyMapping:attributeMapping];
    
    NSDictionary *dictionary = @{ @"arrayOfStrings": @[ @"one", @"two", @"three" ] };
    RKTestUser *user = [RKTestUser user];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    NSError *error = nil;
    BOOL success = [operation performMapping:&error];
    expect(success).to.equal(YES);
    expect(user.data).notTo.beNil();
    NSDictionary *decodedDictionary = [NSKeyedUnarchiver unarchiveObjectWithData:user.data];
    NSArray *expectedValue = @[ @"one", @"two", @"three" ];
    expect(decodedDictionary).to.equal(expectedValue);
}

- (void)testShouldMapNSDateDistantFutureDateStringToADate
{
    [RKObjectMapping resetDefaultDateFormatters];

    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *birthDateMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"birthdate" toKeyPath:@"birthDate"];
    [mapping addPropertyMapping:birthDateMapping];

    NSMutableDictionary *dictionary = [[RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"] mutableCopy];
    [dictionary setObject:@"3001-01-01T00:00:00Z" forKey:@"birthdate"];
    RKTestUser *user = [RKTestUser user];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];

    NSDateFormatter *dateFormatter = [NSDateFormatter new];
    dateFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"];
    [dateFormatter setDateFormat:@"MM/dd/yyyy"];
    assertThat([dateFormatter stringFromDate:user.birthDate], is(equalTo(@"01/01/3001")));
}

- (void)testMappingASingularValueToAnArray
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [mapping addAttributeMappingsFromArray:@[ @"favoriteColors" ]];
    
    NSDictionary *dictionary = @{ @"favoriteColors": @"Blue" };
    RKTestUser *user = [RKTestUser user];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];
    
    assertThat(user.favoriteColors, is(equalTo(@[ @"Blue" ])));
}

- (void)testMappingArrayToMutableArray
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [mapping addAttributeMappingsFromDictionary:@{ @"favoriteColors": @"mutableFavoriteColors" }];
    
    NSDictionary *dictionary = @{ @"favoriteColors": @[ @"Blue", @"Red" ] };
    RKTestUser *user = [RKTestUser user];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];
    
    assertThat(user.mutableFavoriteColors, is(equalTo(@[ @"Blue", @"Red" ])));
    assertThat(user.mutableFavoriteColors, is(instanceOf([NSMutableArray class])));
}

- (void)testMappingASingularValueToASet
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [mapping addAttributeMappingsFromArray:@[ @"friendsSet" ]];
    
    NSDictionary *dictionary = @{ @"friendsSet": @"Jeff" };
    RKTestUser *user = [RKTestUser user];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];
    
    assertThat(user.friendsSet, is(equalTo([NSSet setWithObject:@"Jeff" ])));
}

- (void)testMappingASingularValueToAnOrderedSet
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [mapping addAttributeMappingsFromArray:@[ @"friendsOrderedSet" ]];
    
    NSDictionary *dictionary = @{ @"friendsOrderedSet": @"Jeff" };
    RKTestUser *user = [RKTestUser user];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];
    
    assertThat(user.friendsOrderedSet, is(equalTo([NSOrderedSet orderedSetWithObject:@"Jeff" ])));
}

- (void)testTypeTransformationAtKeyPath
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *websiteMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"this.that" toKeyPath:@"address.addressID"];
    [mapping addPropertyMapping:websiteMapping];
    
    NSDictionary *dictionary = @{ @"this": @{ @"that": @"12345" }};
    RKTestUser *user = [RKTestUser user];
    RKTestAddress *address = [RKTestAddress new];
    user.address = address;
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];
    
    assertThat(user.address.addressID, is(instanceOf([NSNumber class])));
    assertThat(user.address.addressID, is(equalTo(@(12345))));
}

- (void)testThatAttributeMappingToAPrimitiveValueFromNullDoesNotCrash
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [mapping addAttributeMappingsFromArray:@[ @"age" ]];
    
    NSDictionary *dictionary = @{ @"age": [NSNull null] };
    RKTestUser *user = [RKTestUser user];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];
    
    expect(user.age).to.equal(0);
}

- (void)testThatAttributeMappingToAPrimitiveValueFromUnexpectedObjectTypeDoesNotCrash
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [mapping addAttributeMappingsFromArray:@[ @"age" ]];
    
    NSDictionary *dictionary = @{ @"age": @{ @"wrong": @"data" }};
    RKTestUser *user = [RKTestUser user];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];
    
    expect(user.age).to.equal(0);
}

- (void)testThatMappingNullValueToTransformablePropertyDoesNotGenerateWarning
{
    RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
    RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore];
    [humanMapping addAttributeMappingsFromArray:@[ @"catIDs" ]];
    
    NSDictionary *dictionary = @{ @"catsIDs": [NSNull null] };
    RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:human mapping:humanMapping];
    RKManagedObjectMappingOperationDataSource *dataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext cache:nil];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];
    
    expect(human.catIDs).to.beNil();
}

#pragma mark - Relationship Mapping

- (void)testShouldMapANestedObject
{
    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [userMapping addPropertyMapping:nameMapping];
    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];

    RKMapperOperation *mapper = [RKMapperOperation new];
    mapper.mappingOperationDataSource = [RKObjectMappingOperationDataSource new];
    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKTestUser *user = [RKTestUser user];
    BOOL success = [mapper mapRepresentation:userInfo toObject:user atKeyPath:@"" usingMapping:userMapping metadata:nil];
    assertThatBool(success, is(equalToBool(YES)));
    assertThat(user.name, is(equalTo(@"Blake Watters")));
    assertThat(user.address, isNot(nilValue()));
}

- (void)testShouldMapANestedObjectToCollection
{
    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [userMapping addPropertyMapping:nameMapping];
    RKObjectMapping *addressMapping = [RKObjectMapping mappingForClass:[RKTestAddress class]];
    RKAttributeMapping *cityMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"city" toKeyPath:@"city"];
    [addressMapping addPropertyMapping:cityMapping];

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

    RKMapperOperation *mapper = [RKMapperOperation new];
    mapper.mappingOperationDataSource = [RKObjectMappingOperationDataSource new];
    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKTestUser *user = [RKTestUser user];
    BOOL success = [mapper mapRepresentation:userInfo toObject:user atKeyPath:@"" usingMapping:userMapping metadata:nil];
    assertThatBool(success, is(equalToBool(YES)));
    assertThat(user.name, is(equalTo(@"Blake Watters")));
    assertThat(user.friends, isNot(nilValue()));
    assertThatUnsignedInteger([user.friends count], is(equalToInt(1)));
}

- (void)testShouldMapANestedObjectToOrderedSetCollection
{
    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [userMapping addPropertyMapping:nameMapping];
    RKObjectMapping *addressMapping = [RKObjectMapping mappingForClass:[RKTestAddress class]];
    RKAttributeMapping *cityMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"city" toKeyPath:@"city"];
    [addressMapping addPropertyMapping:cityMapping];

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

    RKMapperOperation *mapper = [RKMapperOperation new];
    mapper.mappingOperationDataSource = [RKObjectMappingOperationDataSource new];
    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKTestUser *user = [RKTestUser user];
    BOOL success = [mapper mapRepresentation:userInfo toObject:user atKeyPath:@"" usingMapping:userMapping metadata:nil];
    assertThatBool(success, is(equalToBool(YES)));
    assertThat(user.name, is(equalTo(@"Blake Watters")));
    assertThat(user.friendsOrderedSet, isNot(nilValue()));
    assertThatUnsignedInteger([user.friendsOrderedSet count], is(equalToInt(1)));
}

- (void)testShouldMapANestedObjectCollection
{
    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [userMapping addPropertyMapping:nameMapping];

    RKRelationshipMapping *hasManyMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:@"friends" toKeyPath:@"friends" withMapping:userMapping];
    [userMapping addPropertyMapping:hasManyMapping];

    RKMapperOperation *mapper = [RKMapperOperation new];
    mapper.mappingOperationDataSource = [RKObjectMappingOperationDataSource new];
    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKTestUser *user = [RKTestUser user];
    BOOL success = [mapper mapRepresentation:userInfo toObject:user atKeyPath:@"" usingMapping:userMapping metadata:nil];
    assertThatBool(success, is(equalToBool(YES)));
    assertThat(user.name, is(equalTo(@"Blake Watters")));
    assertThat(user.friends, isNot(nilValue()));
    assertThatUnsignedInteger([user.friends count], is(equalToInt(2)));
    NSArray *names = [NSArray arrayWithObjects:@"Jeremy Ellison", @"Rachit Shukla", nil];
    assertThat([user.friends valueForKey:@"name"], is(equalTo(names)));
}

- (void)testShouldMapANestedArrayIntoASet
{
    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [userMapping addPropertyMapping:nameMapping];

    RKRelationshipMapping *hasManyMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:@"friends" toKeyPath:@"friendsSet" withMapping:userMapping];
    [userMapping addPropertyMapping:hasManyMapping];

    RKMapperOperation *mapper = [RKMapperOperation new];
    mapper.mappingOperationDataSource = [RKObjectMappingOperationDataSource new];
    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKTestUser *user = [RKTestUser user];
    BOOL success = [mapper mapRepresentation:userInfo toObject:user atKeyPath:@"" usingMapping:userMapping metadata:nil];
    assertThatBool(success, is(equalToBool(YES)));
    assertThat(user.name, is(equalTo(@"Blake Watters")));
    assertThat(user.friendsSet, isNot(nilValue()));
    assertThatBool([user.friendsSet isKindOfClass:[NSSet class]], is(equalToBool(YES)));
    assertThatUnsignedInteger([user.friendsSet count], is(equalToInt(2)));
    NSSet *names = [NSSet setWithObjects:@"Jeremy Ellison", @"Rachit Shukla", nil];
    assertThat([user.friendsSet valueForKey:@"name"], is(equalTo(names)));
}

- (void)testShouldMapANestedArrayIntoAnOrderedSet
{
    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [userMapping addPropertyMapping:nameMapping];

    RKRelationshipMapping *hasManyMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:@"friends" toKeyPath:@"friendsOrderedSet" withMapping:userMapping];
    [userMapping addPropertyMapping:hasManyMapping];

    RKMapperOperation *mapper = [RKMapperOperation new];
    mapper.mappingOperationDataSource = [RKObjectMappingOperationDataSource new];
    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKTestUser *user = [RKTestUser user];
    BOOL success = [mapper mapRepresentation:userInfo toObject:user atKeyPath:@"" usingMapping:userMapping metadata:nil];
    assertThatBool(success, is(equalToBool(YES)));
    assertThat(user.name, is(equalTo(@"Blake Watters")));
    assertThat(user.friendsOrderedSet, isNot(nilValue()));
    assertThatBool([user.friendsOrderedSet isKindOfClass:[NSOrderedSet class]], is(equalToBool(YES)));
    assertThatUnsignedInteger([user.friendsOrderedSet count], is(equalToInt(2)));
    NSOrderedSet *names = [NSOrderedSet orderedSetWithObjects:@"Jeremy Ellison", @"Rachit Shukla", nil];
    assertThat([user.friendsOrderedSet valueForKey:@"name"], is(equalTo(names)));
}

- (void)testShouldNotSetThePropertyWhenTheNestedObjectIsIdentical
{
    RKTestUser *user = [RKTestUser user];
    RKTestAddress *address = [RKTestAddress address];
    address.addressID = [NSNumber numberWithInt:1234];
    user.address = address;
    id mockUser = [OCMockObject partialMockForObject:user];
    [[mockUser reject] setAddress:OCMOCK_ANY];

    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [userMapping addPropertyMapping:nameMapping];
    RKObjectMapping *addressMapping = [RKObjectMapping mappingForClass:[RKTestAddress class]];
    RKAttributeMapping *idMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"addressID"];
    [addressMapping addPropertyMapping:idMapping];

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

    RKMapperOperation *mapper = [RKMapperOperation new];
    mapper.mappingOperationDataSource = [RKObjectMappingOperationDataSource new];
    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    [mapper mapRepresentation:userInfo toObject:user atKeyPath:@"" usingMapping:userMapping metadata:nil];
}

- (void)testSkippingOfIdenticalObjectsInformsDelegate
{
    RKTestUser *user = [RKTestUser user];
    RKTestAddress *address = [RKTestAddress address];
    address.addressID = [NSNumber numberWithInt:1234];
    user.address = address;
    id mockUser = [OCMockObject partialMockForObject:user];
    [[mockUser reject] setAddress:OCMOCK_ANY];

    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [userMapping addPropertyMapping:nameMapping];
    RKObjectMapping *addressMapping = [RKObjectMapping mappingForClass:[RKTestAddress class]];
    RKAttributeMapping *idMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"addressID"];
    [addressMapping addPropertyMapping:idMapping];

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

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:userInfo destinationObject:user mapping:userMapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(RKMappingOperationDelegate)];
    [[mockDelegate expect] mappingOperation:operation didNotSetUnchangedValue:address forKeyPath:@"address" usingMapping:hasOneMapping];
    operation.delegate = mockDelegate;
    [operation performMapping:nil];
    [mockDelegate verify];
}

- (void)testShouldNotSetThePropertyWhenTheNestedObjectCollectionIsIdentical
{
    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *idMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"userID"];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [userMapping addPropertyMapping:idMapping];
    [userMapping addPropertyMapping:nameMapping];

    RKRelationshipMapping *hasManyMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:@"friends" toKeyPath:@"friends" withMapping:userMapping];
    [userMapping addPropertyMapping:hasManyMapping];

    RKMapperOperation *mapper = [RKMapperOperation new];
    mapper.mappingOperationDataSource = [RKObjectMappingOperationDataSource new];
    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKTestUser *user = [RKTestUser user];

    // Set the friends up
    RKTestUser *jeremy = [RKTestUser user];
    jeremy.name = @"Jeremy Ellison";
    jeremy.userID = [NSNumber numberWithInt:187];
    RKTestUser *rachit = [RKTestUser user];
    rachit.name = @"Rachit Shukla";
    rachit.userID = [NSNumber numberWithInt:7];
    user.friends = [NSArray arrayWithObjects:jeremy, rachit, nil];

    id mockUser = [OCMockObject partialMockForObject:user];
    [[mockUser reject] setFriends:OCMOCK_ANY];
    [mapper mapRepresentation:userInfo toObject:mockUser atKeyPath:@"" usingMapping:userMapping metadata:nil];
    [mockUser verify];
}

- (void)testShouldOptionallyNilOutTheRelationshipIfItIsMissing
{
    RKTestUser *user = [RKTestUser user];
    RKTestAddress *address = [RKTestAddress address];
    address.addressID = [NSNumber numberWithInt:1234];
    user.address = address;
    id mockUser = [OCMockObject partialMockForObject:user];
    [[mockUser expect] setAddress:nil];

    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [userMapping addPropertyMapping:nameMapping];
    RKObjectMapping *addressMapping = [RKObjectMapping mappingForClass:[RKTestAddress class]];
    RKAttributeMapping *idMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"addressID"];
    [addressMapping addPropertyMapping:idMapping];
    RKRelationshipMapping *relationshipMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:@"address" toKeyPath:@"address" withMapping:addressMapping];
    [userMapping addPropertyMapping:relationshipMapping];

    NSMutableDictionary *dictionary = [[RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"] mutableCopy];
    [dictionary removeObjectForKey:@"address"];
    id mockMapping = [OCMockObject partialMockForObject:userMapping];
    BOOL returnValue = YES;
    [[[mockMapping expect] andReturnValue:OCMOCK_VALUE(returnValue)] setNilForMissingRelationships];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:mockUser mapping:mockMapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    
    NSError *error = nil;
    [operation performMapping:&error];
    [mockUser verify];
}

- (void)testShouldNotNilOutTheRelationshipIfItIsMissingAndCurrentlyNilOnTheTargetObject
{
    RKTestUser *user = [RKTestUser user];
    user.address = nil;
    id mockUser = [OCMockObject partialMockForObject:user];
    [[mockUser reject] setAddress:nil];

    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *nameMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"];
    [userMapping addPropertyMapping:nameMapping];
    RKObjectMapping *addressMapping = [RKObjectMapping mappingForClass:[RKTestAddress class]];
    RKAttributeMapping *idMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"addressID"];
    [addressMapping addPropertyMapping:idMapping];
    RKRelationshipMapping *relationshipMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:@"address" toKeyPath:@"address" withMapping:addressMapping];
    [userMapping addPropertyMapping:relationshipMapping];

    NSMutableDictionary *dictionary = [[RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"] mutableCopy];
    [dictionary removeObjectForKey:@"address"];
    id mockMapping = [OCMockObject partialMockForObject:userMapping];
    BOOL returnValue = YES;
    [[[mockMapping expect] andReturnValue:OCMOCK_VALUE(returnValue)] setNilForMissingRelationships];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:mockUser mapping:mockMapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;

    NSError *error = nil;
    [operation performMapping:&error];
    [mockUser verify];
}

#pragma mark Assignment Policies

- (void)testThatAttemptingToUnionOneToOneRelationshipGeneratesMappingError
{
    RKTestUser *user = [RKTestUser new];
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKObjectMapping *addressMapping = [RKObjectMapping mappingForClass:[RKTestAddress class]];
    [mapping addAttributeMappingsFromArray:@[ @"name" ]];
    RKRelationshipMapping *relationshipMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:@"address" toKeyPath:@"address" withMapping:addressMapping];
    relationshipMapping.assignmentPolicy = RKUnionAssignmentPolicy;
    [mapping addPropertyMapping:relationshipMapping];
    
    NSDictionary *dictionary = @{ @"address": @{ @"city": @"NYC" } };
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    
    NSError *error = nil;
    [operation performMapping:&error];
    expect(error).notTo.beNil();
    expect(error.code).to.equal(RKMappingErrorInvalidAssignmentPolicy);
    expect([error localizedDescription]).to.equal(@"Invalid assignment policy: cannot union a one-to-one relationship.");
}

- (void)testUnionAssignmentPolicyWithSet
{
    RKTestUser *user = [RKTestUser new];
    RKTestUser *friend = [RKTestUser new];
    friend.name = @"Jeff";
    user.friendsSet = [NSSet setWithObject:friend];
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [mapping addAttributeMappingsFromArray:@[ @"name" ]];
    RKRelationshipMapping *relationshipMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:@"friendsSet" toKeyPath:@"friendsSet" withMapping:mapping];
    relationshipMapping.assignmentPolicy = RKUnionAssignmentPolicy;
    [mapping addPropertyMapping:relationshipMapping];
    
    NSDictionary *dictionary = @{ @"friendsSet": @[ @{ @"name": @"Zach" } ] };
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    
    NSError *error = nil;
    [operation performMapping:&error];
    expect([user.friendsSet count]).to.equal(2);
    NSArray *names = [user.friendsSet valueForKey:@"name"];
    assertThat(names, hasItems(@"Jeff", @"Zach", nil));
}

- (void)testUnionAssignmentPolicyWithOrderedSet
{
    RKTestUser *user = [RKTestUser new];
    RKTestUser *friend = [RKTestUser new];
    friend.name = @"Jeff";
    user.friendsOrderedSet = [NSOrderedSet orderedSetWithObject:friend];
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [mapping addAttributeMappingsFromArray:@[ @"name" ]];
    RKRelationshipMapping *relationshipMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:@"friendsOrderedSet" toKeyPath:@"friendsOrderedSet" withMapping:mapping];
    relationshipMapping.assignmentPolicy = RKUnionAssignmentPolicy;
    [mapping addPropertyMapping:relationshipMapping];
    
    NSDictionary *dictionary = @{ @"friendsOrderedSet": @[ @{ @"name": @"Zach" } ] };
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    
    NSError *error = nil;
    [operation performMapping:&error];
    expect([user.friendsOrderedSet count]).to.equal(2);
    NSArray *names = [user.friendsOrderedSet valueForKey:@"name"];
    assertThat(names, hasItems(@"Jeff", @"Zach", nil));
}

- (void)testUnionAssignmentPolicyWithArray
{
    RKTestUser *user = [RKTestUser new];
    RKTestUser *friend = [RKTestUser new];
    friend.name = @"Jeff";
    user.friends = [NSArray arrayWithObject:friend];
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [mapping addAttributeMappingsFromArray:@[ @"name" ]];
    RKRelationshipMapping *relationshipMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:@"friends" toKeyPath:@"friends" withMapping:mapping];
    relationshipMapping.assignmentPolicy = RKUnionAssignmentPolicy;
    [mapping addPropertyMapping:relationshipMapping];
    
    NSDictionary *dictionary = @{ @"friends": @[ @{ @"name": @"Zach" } ] };
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    
    NSError *error = nil;
    [operation performMapping:&error];
    expect([user.friends count]).to.equal(2);
    NSArray *names = [user.friends valueForKey:@"name"];
    assertThat(names, hasItems(@"Jeff", @"Zach", nil));
}

- (void)testUnionAssignmentPolicyWithNilValue
{
    RKTestUser *user = [RKTestUser new];
    user.friends = nil;
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [mapping addAttributeMappingsFromArray:@[ @"name" ]];
    RKRelationshipMapping *relationshipMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:@"friends" toKeyPath:@"friends" withMapping:mapping];
    relationshipMapping.assignmentPolicy = RKUnionAssignmentPolicy;
    [mapping addPropertyMapping:relationshipMapping];
    
    NSDictionary *dictionary = @{ @"friends": @[ @{ @"name": @"Zach" } ] };
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    
    NSError *error = nil;
    [operation performMapping:&error];
    expect([user.friends count]).to.equal(1);
    NSArray *names = [user.friends valueForKey:@"name"];
    assertThat(names, hasItems(@"Zach", nil));
}

- (void)testReplacementPolicyForUnmanagedRelationship
{
    RKTestUser *user = [RKTestUser new];
    RKTestUser *friend = [RKTestUser new];
    friend.name = @"Jeff";
    user.friends = [NSArray arrayWithObject:friend];
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [mapping addAttributeMappingsFromArray:@[ @"name" ]];
    RKRelationshipMapping *relationshipMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:@"friends" toKeyPath:@"friends" withMapping:mapping];
    relationshipMapping.assignmentPolicy = RKReplaceAssignmentPolicy;
    [mapping addPropertyMapping:relationshipMapping];
    
    NSDictionary *dictionary = @{ @"friends": @[ @{ @"name": @"Zach" } ] };
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    
    NSError *error = nil;
    [operation performMapping:&error];
    expect([user.friends count]).to.equal(1);
    NSArray *names = [user.friends valueForKey:@"name"];
    assertThat(names, hasItems(@"Zach", nil));
}

- (void)testReplacmentPolicyForToManyCoreDataRelationshipDeletesExistingValues
{
    RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
    RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext];
    RKCat *existingCat = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext];
    existingCat.name = @"Lola";
    human.cats = [NSSet setWithObject:existingCat];
    
    RKEntityMapping *entityMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore];
    [entityMapping addAttributeMappingsFromArray:@[ @"name" ]];
    RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore];
    [catMapping addAttributeMappingsFromArray:@[ @"name" ]];
    catMapping.identificationAttributes = @[ @"name" ];
    RKRelationshipMapping *relationshipMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:@"cats" toKeyPath:@"cats" withMapping:catMapping];
    relationshipMapping.assignmentPolicy = RKReplaceAssignmentPolicy;
    [entityMapping addPropertyMapping:relationshipMapping];
    
    NSDictionary *dictionary = @{ @"name": @"Blake", @"cats": @[ @{ @"name": @"Roy" } ] };
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:human mapping:entityMapping];
    RKManagedObjectMappingOperationDataSource *dataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext cache:nil];
    operation.dataSource = dataSource;
    
    NSError *error = nil;
    [operation performMapping:&error];
    expect([human.cats count]).to.equal(1);
    NSArray *names = [human.cats valueForKey:@"name"];
    assertThat(names, hasItems(@"Roy", nil));
    expect([existingCat isDeleted]).to.equal(YES);
}

- (void)testReplacmentPolicyForToOneCoreDataRelationshipDeletesExistingValues
{
    RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
    RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext];
    RKCat *existingCat = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext];
    existingCat.name = @"Lola";
    human.favoriteCat = existingCat;
    
    RKEntityMapping *entityMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore];
    [entityMapping addAttributeMappingsFromArray:@[ @"name" ]];
    RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore];
    [catMapping addAttributeMappingsFromArray:@[ @"name" ]];
    catMapping.identificationAttributes = @[ @"name" ];
    RKRelationshipMapping *relationshipMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:@"favoriteCat" toKeyPath:@"favoriteCat" withMapping:catMapping];
    relationshipMapping.assignmentPolicy = RKReplaceAssignmentPolicy;
    [entityMapping addPropertyMapping:relationshipMapping];
    
    NSDictionary *dictionary = @{ @"name": @"Blake", @"favoriteCat": @{ @"name": @"Roy" } };
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:human mapping:entityMapping];
    RKManagedObjectMappingOperationDataSource *dataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext cache:nil];
    operation.dataSource = dataSource;
    
    NSError *error = nil;
    [operation performMapping:&error];
    expect(human.favoriteCat.name).to.equal(@"Roy");
    expect([existingCat isDeleted]).to.equal(YES);
}

#pragma mark - RKDynamicMapping

- (void)testShouldMapASingleObjectDynamically
{
    RKObjectMapping *boyMapping = [RKObjectMapping mappingForClass:[Boy class]];
    [boyMapping addAttributeMappingsFromArray:@[@"name"]];
    RKObjectMapping *girlMapping = [RKObjectMapping mappingForClass:[Girl class]];
    [girlMapping addAttributeMappingsFromArray:@[@"name"]];
    RKDynamicMapping *dynamicMapping = [RKDynamicMapping new];
    [dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) {
        if ([[representation valueForKey:@"type"] isEqualToString:@"Boy"]) {
            return boyMapping;
        } else if ([[representation valueForKey:@"type"] isEqualToString:@"Girl"]) {
            return girlMapping;
        }

        return nil;
    }];

    NSMutableDictionary *mappingsDictionary = [NSMutableDictionary dictionary];
    [mappingsDictionary setObject:dynamicMapping forKey:[NSNull null]];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"boy.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:mappingsDictionary];
    [mapper start];
    Boy *user = [mapper.mappingResult firstObject];
    assertThat(user, is(instanceOf([Boy class])));
    assertThat(user.name, is(equalTo(@"Blake Watters")));
}

- (void)testShouldMapASingleObjectDynamicallyWithADeclarativeMatcher
{
    RKObjectMapping *boyMapping = [RKObjectMapping mappingForClass:[Boy class]];
    [boyMapping addAttributeMappingsFromArray:@[@"name"]];
    RKObjectMapping *girlMapping = [RKObjectMapping mappingForClass:[Girl class]];
    [girlMapping addAttributeMappingsFromArray:@[@"name"]];
    RKDynamicMapping *dynamicMapping = [RKDynamicMapping new];
    [dynamicMapping addMatcher:[RKObjectMappingMatcher matcherWithKeyPath:@"type" expectedValue:@"Boy" objectMapping:boyMapping]];
    [dynamicMapping addMatcher:[RKObjectMappingMatcher matcherWithKeyPath:@"type" expectedValue:@"Girl" objectMapping:girlMapping]];

    NSMutableDictionary *mappingsDictionary = [NSMutableDictionary dictionary];
    [mappingsDictionary setObject:dynamicMapping forKey:[NSNull null]];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"boy.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:mappingsDictionary];
    [mapper start];
    Boy *user = [mapper.mappingResult firstObject];
    assertThat(user, is(instanceOf([Boy class])));
    assertThat(user.name, is(equalTo(@"Blake Watters")));
}

- (void)testShouldACollectionOfObjectsDynamically
{
    RKObjectMapping *boyMapping = [RKObjectMapping mappingForClass:[Boy class]];
    [boyMapping addAttributeMappingsFromArray:@[@"name"]];
    RKObjectMapping *girlMapping = [RKObjectMapping mappingForClass:[Girl class]];
    [girlMapping addAttributeMappingsFromArray:@[@"name"]];
    RKDynamicMapping *dynamicMapping = [RKDynamicMapping new];
    [dynamicMapping addMatcher:[RKObjectMappingMatcher matcherWithKeyPath:@"type" expectedValue:@"Boy" objectMapping:boyMapping]];
    [dynamicMapping addMatcher:[RKObjectMappingMatcher matcherWithKeyPath:@"type" expectedValue:@"Girl" objectMapping:girlMapping]];

    NSMutableDictionary *mappingsDictionary = [NSMutableDictionary dictionary];
    [mappingsDictionary setObject:dynamicMapping forKey:[NSNull null]];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"mixed.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:mappingsDictionary];
    [mapper start];
    NSArray *objects = [mapper.mappingResult array];
    expect(objects).to.haveCountOf(2);
    expect(objects[0]).to.beInstanceOf([Boy class]);
    expect(objects[1]).to.beInstanceOf([Girl class]);
    Boy *boy = [objects objectAtIndex:0];
    Girl *girl = [objects objectAtIndex:1];
    expect(boy.name).to.equal(@"Blake Watters");
    expect(girl.name).to.equal(@"Sarah");
}

- (void)testShouldMapARelationshipDynamically
{
    RKObjectMapping *boyMapping = [RKObjectMapping mappingForClass:[Boy class]];
    [boyMapping addAttributeMappingsFromArray:@[@"name"]];
    RKObjectMapping *girlMapping = [RKObjectMapping mappingForClass:[Girl class]];
    [girlMapping addAttributeMappingsFromArray:@[@"name"]];
    RKDynamicMapping *dynamicMapping = [RKDynamicMapping new];
    [dynamicMapping addMatcher:[RKObjectMappingMatcher matcherWithKeyPath:@"type" expectedValue:@"Boy" objectMapping:boyMapping]];
    [dynamicMapping addMatcher:[RKObjectMappingMatcher matcherWithKeyPath:@"type" expectedValue:@"Girl" objectMapping:girlMapping]];
    [boyMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"friends" toKeyPath:@"friends" withMapping:dynamicMapping]];;

    NSMutableDictionary *mappingsDictionary = [NSMutableDictionary dictionary];
    [mappingsDictionary setObject:dynamicMapping forKey:[NSNull null]];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"friends.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:mappingsDictionary];
    [mapper start];
    Boy *blake = [mapper.mappingResult firstObject];
    NSArray *friends = blake.friends;

    assertThat(friends, hasCountOf(2));
    assertThat([friends objectAtIndex:0], is(instanceOf([Boy class])));
    assertThat([friends objectAtIndex:1], is(instanceOf([Girl class])));
    Boy *boy = [friends objectAtIndex:0];
    Girl *girl = [friends objectAtIndex:1];
    assertThat(boy.name, is(equalTo(@"John Doe")));
    assertThat(girl.name, is(equalTo(@"Jane Doe")));
}

- (void)testShouldBeAbleToDeclineMappingAnObjectByReturningANilObjectMapping
{
    RKObjectMapping *boyMapping = [RKObjectMapping mappingForClass:[Boy class]];
    [boyMapping addAttributeMappingsFromArray:@[@"name"]];
    RKObjectMapping *girlMapping = [RKObjectMapping mappingForClass:[Girl class]];
    [girlMapping addAttributeMappingsFromArray:@[@"name"]];
    RKDynamicMapping *dynamicMapping = [RKDynamicMapping new];
    [dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) {
        if ([[representation valueForKey:@"type"] isEqualToString:@"Boy"]) {
            return boyMapping;
        } else if ([[representation valueForKey:@"type"] isEqualToString:@"Girl"]) {
            // NO GIRLS ALLOWED(*$!)(*
            return nil;
        }

        return nil;
    }];

    NSMutableDictionary *mappingsDictionary = [NSMutableDictionary dictionary];
    [mappingsDictionary setObject:dynamicMapping forKey:[NSNull null]];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"mixed.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:mappingsDictionary];
    [mapper start];
    NSArray *boys = [mapper.mappingResult array];
    assertThat(boys, hasCountOf(1));
    Boy *user = [boys objectAtIndex:0];
    assertThat(user, is(instanceOf([Boy class])));
    assertThat(user.name, is(equalTo(@"Blake Watters")));
}

- (void)testShouldBeAbleToDeclineMappingObjectsInARelationshipByReturningANilObjectMapping
{
    RKObjectMapping *boyMapping = [RKObjectMapping mappingForClass:[Boy class]];
    [boyMapping addAttributeMappingsFromArray:@[@"name"]];
    RKObjectMapping *girlMapping = [RKObjectMapping mappingForClass:[Girl class]];
    [girlMapping addAttributeMappingsFromArray:@[@"name"]];
    RKDynamicMapping *dynamicMapping = [RKDynamicMapping new];
    [dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) {
        if ([[representation valueForKey:@"type"] isEqualToString:@"Boy"]) {
            return boyMapping;
        } else if ([[representation valueForKey:@"type"] isEqualToString:@"Girl"]) {
            // NO GIRLS ALLOWED(*$!)(*
            return nil;
        }

        return nil;
    }];
    [boyMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"friends" toKeyPath:@"friends" withMapping:dynamicMapping]];;

    NSMutableDictionary *mappingsDictionary = [NSMutableDictionary dictionary];
    [mappingsDictionary setObject:dynamicMapping forKey:[NSNull null]];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"friends.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:mappingsDictionary];
    [mapper start];
    Boy *blake = [mapper.mappingResult firstObject];
    assertThat(blake, is(notNilValue()));
    assertThat(blake.name, is(equalTo(@"Blake Watters")));
    assertThat(blake, is(instanceOf([Boy class])));
    NSArray *friends = blake.friends;

    assertThat(friends, hasCountOf(1));
    assertThat([friends objectAtIndex:0], is(instanceOf([Boy class])));
    Boy *boy = [friends objectAtIndex:0];
    assertThat(boy.name, is(equalTo(@"John Doe")));
}

- (void)testShouldMapATargetObjectWithADynamicMapping
{
    RKObjectMapping *boyMapping = [RKObjectMapping mappingForClass:[Boy class]];
    [boyMapping addAttributeMappingsFromArray:@[@"name"]];
    RKDynamicMapping *dynamicMapping = [RKDynamicMapping new];
    [dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) {
        if ([[representation valueForKey:@"type"] isEqualToString:@"Boy"]) {
            return boyMapping;
        }

        return nil;
    }];

    NSMutableDictionary *mappingsDictionary = [NSMutableDictionary dictionary];
    [mappingsDictionary setObject:dynamicMapping forKey:[NSNull null]];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"boy.json"];
    Boy *blake = [Boy new];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:mappingsDictionary];
    mapper.targetObject = blake;
    [mapper start];
    Boy *user = [mapper.mappingResult firstObject];
    assertThat(user, is(instanceOf([Boy class])));
    assertThat(user.name, is(equalTo(@"Blake Watters")));
}

- (void)testShouldFailWithAnErrorIfATargetObjectIsProvidedAndTheDynamicMappingReturnsNil
{
    RKObjectMapping *boyMapping = [RKObjectMapping mappingForClass:[Boy class]];
    [boyMapping addAttributeMappingsFromArray:@[@"name"]];
    RKDynamicMapping *dynamicMapping = [RKDynamicMapping new];
    [dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) {
        return nil;
    }];

    NSMutableDictionary *mappingsDictionary = [NSMutableDictionary dictionary];
    [mappingsDictionary setObject:dynamicMapping forKey:[NSNull null]];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"boy.json"];
    Boy *blake = [Boy new];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:mappingsDictionary];
    mapper.targetObject = blake;
    [mapper start];
    Boy *user = [mapper.mappingResult firstObject];
    assertThat(user, is(nilValue()));
    assertThat(mapper.error, is(notNilValue()));
}

- (void)testShouldFailWithAnErrorIfATargetObjectIsProvidedAndTheDynamicMappingReturnsTheIncorrectType
{
    RKObjectMapping *girlMapping = [RKObjectMapping mappingForClass:[Girl class]];
    [girlMapping addAttributeMappingsFromArray:@[@"name"]];
    RKDynamicMapping *dynamicMapping = [RKDynamicMapping new];
    [dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) {
        if ([[representation valueForKey:@"type"] isEqualToString:@"Girl"]) {
            return girlMapping;
        }

        return nil;
    }];

    NSMutableDictionary *mappingsDictionary = [NSMutableDictionary dictionary];
    [mappingsDictionary setObject:dynamicMapping forKey:[NSNull null]];

    id userInfo = [RKTestFixture parsedObjectWithContentsOfFixture:@"girl.json"];
    Boy *blake = [Boy new];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:userInfo mappingsDictionary:mappingsDictionary];
    mapper.targetObject = blake;
    [mapper start];
    Boy *user = [mapper.mappingResult firstObject];
    assertThat(user, is(nilValue()));
    assertThat(mapper.error, is(notNilValue()));
}

#pragma mark - Date and Time Formatting

- (void)testShouldAutoConfigureDefaultDateFormatters
{
    [RKObjectMapping resetDefaultDateFormatters];
    NSArray *dateFormatters = [RKObjectMapping defaultDateFormatters];
    expect(dateFormatters).to.haveCountOf(5);
    expect([dateFormatters[0] dateFormat]).to.equal(@"yyyy-MM-dd");
    expect([dateFormatters[1] dateFormat]).to.equal(@"yyyy-MM-dd'T'HH:mm:ss'Z'");
    expect([dateFormatters[2] dateFormat]).to.equal(@"MM/dd/yyyy");

    NSTimeZone *UTCTimeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"];
    expect([[dateFormatters objectAtIndex:0] timeZone]).to.equal(UTCTimeZone);
    expect([[dateFormatters objectAtIndex:1] timeZone]).to.equal(UTCTimeZone);
    expect([[dateFormatters objectAtIndex:2] timeZone]).to.equal(UTCTimeZone);
}

- (void)testShouldLetYouSetTheDefaultDateFormatters
{
    NSDateFormatter *dateFormatter = [NSDateFormatter new];
    NSArray *dateFormatters = [NSArray arrayWithObject:dateFormatter];
    [RKObjectMapping setDefaultDateFormatters:dateFormatters];
    assertThat([RKObjectMapping defaultDateFormatters], is(equalTo(dateFormatters)));
}

- (void)testShouldLetYouAppendADateFormatterToTheList
{
    [RKObjectMapping resetDefaultDateFormatters];
    assertThat([RKObjectMapping defaultDateFormatters], hasCountOf(5));
    NSDateFormatter *dateFormatter = [NSDateFormatter new];
    [RKObjectMapping addDefaultDateFormatter:dateFormatter];
    assertThat([RKObjectMapping defaultDateFormatters], hasCountOf(6));
}

- (void)testShouldAllowNewlyAddedDateFormatterToRunFirst
{
    [RKObjectMapping setDefaultDateFormatters:nil];
    NSDateFormatter *newDateFormatter = [NSDateFormatter new];
    [newDateFormatter setDateFormat:@"dd/MM/yyyy"];
    [RKObjectMapping addDefaultDateFormatter:newDateFormatter];

    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *birthDateMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"favorite_date" toKeyPath:@"favoriteDate"];
    [mapping addPropertyMapping:birthDateMapping];

    NSDictionary *dictionary = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    RKTestUser *user = [RKTestUser user];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:dictionary destinationObject:user mapping:mapping];
    NSError *error = nil;
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    [operation performMapping:&error];

    NSDateFormatter *dateFormatter = [NSDateFormatter new];
    [dateFormatter setDateFormat:@"MM/dd/yyyy"];

    /*
     If RKObjectMappingOperation is using the date formatter set above, we're
     going to get a really wonky date, which is what we are testing for.
     */
    assertThat([dateFormatter stringFromDate:user.favoriteDate], is(equalTo(@"01/03/2012")));
}

- (void)testShouldLetYouConfigureANewDateFormatterFromAStringAndATimeZone
{
    [RKObjectMapping resetDefaultDateFormatters];
    assertThat([RKObjectMapping defaultDateFormatters], hasCountOf(5));
    NSTimeZone *EDTTimeZone = [NSTimeZone timeZoneWithAbbreviation:@"EDT"];
    [RKObjectMapping addDefaultDateFormatterForString:@"mm/dd/YYYY" inTimeZone:EDTTimeZone];
    assertThat([RKObjectMapping defaultDateFormatters], hasCountOf(6));
    NSDateFormatter *dateFormatter = [[RKObjectMapping defaultDateFormatters] objectAtIndex:0];
    assertThat(dateFormatter.timeZone, is(equalTo(EDTTimeZone)));
}

- (void)testShouldReturnNilForEmptyDateValues
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *birthDateMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"birthdate" toKeyPath:@"birthDate"];
    [mapping addPropertyMapping:birthDateMapping];

    NSDictionary *dictionary = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    NSMutableDictionary *mutableDictionary = [dictionary mutableCopy];
    [mutableDictionary setValue:@"" forKey:@"birthdate"];
    RKTestUser *user = [RKTestUser user];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:mutableDictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];

    assertThat(user.birthDate, is(equalTo(nil)));
}

- (void)testShouldConfigureANewDateFormatterInTheUTCTimeZoneIfPassedANilTimeZone
{
    [RKObjectMapping resetDefaultDateFormatters];
    assertThat([RKObjectMapping defaultDateFormatters], hasCountOf(5));
    [RKObjectMapping addDefaultDateFormatterForString:@"mm/dd/YYYY" inTimeZone:nil];
    assertThat([RKObjectMapping defaultDateFormatters], hasCountOf(6));
    NSDateFormatter *dateFormatter = [[RKObjectMapping defaultDateFormatters] objectAtIndex:0];
    NSTimeZone *UTCTimeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"];
    assertThat(dateFormatter.timeZone, is(equalTo(UTCTimeZone)));
}

- (void)testShouldEnsureRailsDatesAreParsedInUTCTimeZone
{
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    RKAttributeMapping *birthDateMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"birthdate" toKeyPath:@"birthDate"];
    [mapping addPropertyMapping:birthDateMapping];

    NSDictionary *dictionary = [RKTestFixture parsedObjectWithContentsOfFixture:@"user.json"];
    NSMutableDictionary *mutableDictionary = [dictionary mutableCopy];
    [mutableDictionary setValue:@"2012-03-01" forKey:@"birthdate"];
    RKTestUser *user = [RKTestUser user];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:mutableDictionary destinationObject:user mapping:mapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    operation.dataSource = dataSource;
    NSError *error = nil;
    [operation performMapping:&error];

    NSDateComponents *components = [[NSDateComponents alloc] init];
    components.year = 2012;
    components.month = 3;
    components.day = 1;
    components.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
    NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    NSDate *date = [calendar dateFromComponents:components];

    expect(user.birthDate).to.equal(date);
}

#pragma mark - Misc

- (void)testUpdatingArrayOfExistingCats
{
    RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
    NSArray *array = [RKTestFixture parsedObjectWithContentsOfFixture:@"ArrayOfHumans.json"];
    RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore];
    [humanMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"railsID"]];
    humanMapping.identificationAttributes = @[ @"railsID" ];
    NSMutableDictionary *mappingsDictionary = [NSMutableDictionary dictionary];
    [mappingsDictionary setObject:humanMapping forKey:@"human"];

    // Create instances that should match the fixture
    RKHuman *human1 = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext];
    human1.railsID = [NSNumber numberWithInt:201];
    RKHuman *human2 = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext];
    human2.railsID = [NSNumber numberWithInt:202];
    [managedObjectStore.persistentStoreManagedObjectContext save:nil];

    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:array mappingsDictionary:mappingsDictionary];
    RKFetchRequestManagedObjectCache *managedObjectCache = [[RKFetchRequestManagedObjectCache alloc] init];
    mapper.mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext
                                                                                                                  cache:managedObjectCache];
                                                                                                                  
    [mapper start];
    RKMappingResult *result = mapper.mappingResult;
    assertThat(result, is(notNilValue()));

    NSArray *humans = [result array];
    assertThat(humans, hasCountOf(2));
    assertThat([humans objectAtIndex:0], is(equalTo(human1)));
    assertThat([humans objectAtIndex:1], is(equalTo(human2)));
}

- (void)testMappingMultipleKeyPathsAtRootOfObject
{
    RKObjectMapping *mapping1 = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [mapping1 addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"prodId" toKeyPath:@"userID"]];
    
    RKObjectMapping *mapping2 = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [mapping2 addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"catId" toKeyPath:@"userID"]];
    
    NSDictionary *dictionary = [RKTestFixture parsedObjectWithContentsOfFixture:@"SameKeyDifferentTargetClasses.json"];
    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:dictionary mappingsDictionary:@{ @"products": mapping1, @"categories": mapping2 }];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    mapper.mappingOperationDataSource = dataSource;
    [mapper start];
    
    expect(mapper.error).to.beNil();
    expect(mapper.mappingResult).notTo.beNil();
    expect([mapper.mappingResult array]).to.haveCountOf(4);
}

- (void)testAggregatingPropertyMappingUsingNilKeyPath
{
    NSDictionary *objectRepresentation = @{ @"name": @"Blake", @"latitude": @(125.55), @"longitude": @(200.5) };
    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [userMapping addAttributeMappingsFromArray:@[ @"name" ]];
    RKObjectMapping *coordinateMapping = [RKObjectMapping mappingForClass:[RKTestCoordinate class]];
    [coordinateMapping addAttributeMappingsFromArray:@[ @"latitude", @"longitude" ]];
    [userMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:nil toKeyPath:@"coordinate" withMapping:coordinateMapping]];
    RKTestUser *user = [RKTestUser new];
    RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:objectRepresentation destinationObject:user mapping:userMapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    mappingOperation.dataSource = dataSource;
    [mappingOperation start];
    expect(mappingOperation.error).to.beNil();
    expect(user.coordinate).notTo.beNil();
    expect(user.coordinate.latitude).to.equal(125.55);
    expect(user.coordinate.longitude).to.equal(200.5);
}

- (void)testThatAggregatedRelationshipMappingsAreOnlyAppliedIfThereIsAtLeastOneValueInTheRepresentation
{
    NSDictionary *objectRepresentation = @{ @"name": @"Blake" };
    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [userMapping addAttributeMappingsFromArray:@[ @"name" ]];
    RKObjectMapping *coordinateMapping = [RKObjectMapping mappingForClass:[RKTestCoordinate class]];
    [coordinateMapping addAttributeMappingsFromArray:@[ @"latitude", @"longitude" ]];
    [userMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:nil toKeyPath:@"coordinate" withMapping:coordinateMapping]];
    RKTestUser *user = [RKTestUser new];
    RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:objectRepresentation destinationObject:user mapping:userMapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    mappingOperation.dataSource = dataSource;
    [mappingOperation start];
    expect(mappingOperation.error).to.beNil();
    expect(user.coordinate).to.beNil();
}

#pragma mark - Metadata Mapping

- (void)testMappingURLFromMetadata
{
    NSDictionary *objectRepresentation = @{ @"name": @"Blake Watters" };
    NSDictionary *metadata = @{ @"HTTP": @{ @"request": @{ @"URL": [NSURL URLWithString:@"http://restkit.org/"] } } };
    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [userMapping addAttributeMappingsFromDictionary:@{ @"name": @"name", @"@metadata.HTTP.request.URL": @"website" }];
    RKTestUser *user = [RKTestUser new];
    RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:objectRepresentation destinationObject:user mapping:userMapping];
    mappingOperation.metadata = metadata;
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    mappingOperation.dataSource = dataSource;
    [mappingOperation start];
    expect(mappingOperation.error).to.beNil();
    expect(user.name).to.equal(@"Blake Watters");
    expect([user.website absoluteString]).to.equal(@"http://restkit.org/");
}

- (void)testMappingHeadersFromMetadata
{
    NSDictionary *objectRepresentation = @{ @"name": @"Blake Watters" };
    NSDictionary *metadata = @{ @"HTTP": @{ @"request": @{ @"headers": @{ @"Content-Type": @"text/html; charset=UTF-8" } } } };
    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [userMapping addAttributeMappingsFromDictionary:@{ @"name": @"name", @"@metadata.HTTP.request.headers.Content-Type": @"emailAddress" }];
    RKTestUser *user = [RKTestUser new];
    RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:objectRepresentation destinationObject:user mapping:userMapping];
    mappingOperation.metadata = metadata;
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    mappingOperation.dataSource = dataSource;
    [mappingOperation start];
    expect(mappingOperation.error).to.beNil();
    expect(user.name).to.equal(@"Blake Watters");
    expect(user.emailAddress).to.equal(@"text/html; charset=UTF-8");
}

- (void)testMappingCollectionIndexFromRelationship
{
    // Do same as above, but use the "friends" relationship
    NSDictionary *objectRepresentation = @{ @"name": @"Blake Watters", @"friends": @[ @{ @"name": @"Jeff Arena" } ] };
    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [userMapping addAttributeMappingsFromDictionary:@{ @"name": @"name", @"@metadata.mapping.collectionIndex": @"userID" }];
    [userMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"friends" toKeyPath:@"friendsSet" withMapping:userMapping]];
    RKTestUser *user = [RKTestUser new];
    RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:objectRepresentation destinationObject:user mapping:userMapping];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    mappingOperation.dataSource = dataSource;
    [mappingOperation start];
    expect(mappingOperation.error).to.beNil();
    expect(user.name).to.equal(@"Blake Watters");
    expect(user.friendsSet).to.haveCountOf(1);
    RKTestUser *jeff = [user.friendsSet anyObject];
    expect(jeff.name).to.equal(@"Jeff Arena");
    expect(jeff.userID).notTo.beNil();
    expect(jeff.userID).to.equal(0);
}

- (void)testMappingCollectionIndexFromMapperOperation
{
    NSArray *representations = @[ @{ @"name": @"Blake Watters" }, @{ @"name": @"Jeff Arena" }, @{ @"name": @"Dan Gellert" }, @{ @"name": @"Zachary Einzig" } ];
    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [userMapping addAttributeMappingsFromDictionary:@{ @"name": @"name", @"@metadata.mapping.collectionIndex": @"position" }];
    RKMapperOperation *mapperOperation = [[RKMapperOperation alloc] initWithRepresentation:representations mappingsDictionary:@{ [NSNull null]: userMapping }];
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    mapperOperation.mappingOperationDataSource = dataSource;
    NSError *error = nil;
    [mapperOperation execute:&error];
    NSArray *users = [mapperOperation.mappingResult array];
    expect(error).to.beNil();
    expect(users).to.haveCountOf(4);
    NSArray *expectedUsers = @[ @"Blake Watters", @"Jeff Arena", @"Dan Gellert", @"Zachary Einzig" ];
    [expectedUsers enumerateObjectsUsingBlock:^(NSString *name, NSUInteger index, BOOL *stop) {
        RKTestUser *user = [users objectAtIndex:index];
        expect(user.name).to.equal(name);
        expect(user.position).to.equal(index);
    }];
}

- (void)testMetadataIsMerged
{
    NSArray *representations = @[ @{ @"name": @"Blake Watters" } ];
    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [userMapping addAttributeMappingsFromDictionary:@{ @"name": @"name", @"@metadata.mapping.collectionIndex": @"position", @"@metadata.mapping.country": @"country" }];
    RKMapperOperation *mapperOperation = [[RKMapperOperation alloc] initWithRepresentation:representations mappingsDictionary:@{ [NSNull null]: userMapping }];
    mapperOperation.metadata = @{ @"mapping": @{ @"country": @"United States of America" } };
    RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new];
    mapperOperation.mappingOperationDataSource = dataSource;
    NSError *error = nil;
    [mapperOperation execute:&error];
    RKTestUser *user = [mapperOperation.mappingResult firstObject];
    expect(error).to.beNil();
    expect(user.name).to.equal(@"Blake Watters");
    expect(user.position).to.equal(0);
    expect(user.country).to.equal(@"United States of America");
}

// RKResponseMapperOperation sets up Metadata for HTTP request/response
// RKMapperOperation sets up mapping.rootKey, also collectionIndex for outer arrays

@end
