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

#import "RKTestEnvironment.h"
#import "RKObjectManager.h"
#import "RKManagedObjectStore.h"
#import "RKEntityMapping.h"
#import "RKHuman.h"
#import "RKCat.h"
#import "RKTestUser.h"
#import "RKObjectMapperTestModel.h"
#import "RKDynamicMapping.h"
#import "RKTestAddress.h"
#import "RKPost.h"

@interface RKSubclassedTestModel : RKObjectMapperTestModel
@end

@implementation RKSubclassedTestModel
@end

@interface RKTestAFHTTPClient : AFHTTPClient
@end

@implementation RKTestAFHTTPClient

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                      path:(NSString *)path
                                parameters:(NSDictionary *)parameters
{
    NSMutableURLRequest *request = [super requestWithMethod:method path:path parameters:parameters];
    [request setAllHTTPHeaderFields:@{@"test": @"value", @"Accept": @"text/html"}];
    return request;
}

@end

@interface RKTestHTTPRequestOperation : RKHTTPRequestOperation
@end
@implementation RKTestHTTPRequestOperation : RKHTTPRequestOperation
@end

@interface RKTestObjectRequestOperation : RKObjectRequestOperation
@end

@implementation RKTestObjectRequestOperation

+ (BOOL)canProcessRequest:(NSURLRequest *)request
{
    return [[request.URL relativePath] isEqualToString:@"/match"];
}

@end

@interface RKTestManagedObjectRequestOperation : RKManagedObjectRequestOperation
@end

@implementation RKTestManagedObjectRequestOperation

+ (BOOL)canProcessRequest:(NSURLRequest *)request
{
    return [[request.URL relativePath] isEqualToString:@"/match"];
}

@end

@interface RKObjectManagerTest : RKTestCase

@property (nonatomic, strong) RKObjectManager *objectManager;
@property (nonatomic, strong) RKRoute *humanGETRoute;
@property (nonatomic, strong) RKRoute *humanPOSTRoute;
@property (nonatomic, strong) RKRoute *humanDELETERoute;
@property (nonatomic, strong) RKRoute *humanCatsRoute;
@property (nonatomic, strong) RKRoute *humansCollectionRoute;

@end

@implementation RKObjectManagerTest

- (void)setUp
{
    [RKTestFactory setUp];
    
    self.objectManager = [RKTestFactory objectManager];
    self.objectManager.managedObjectStore = [RKTestFactory managedObjectStore];
    [RKObjectManager setSharedManager:self.objectManager];
    NSError *error;
    [self.objectManager.managedObjectStore resetPersistentStores:&error];
    
    RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:_objectManager.managedObjectStore];
    [humanMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"]];
    [humanMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"nick-name" toKeyPath:@"nickName"]];
    [humanMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"birthday" toKeyPath:@"birthday"]];
    [humanMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"sex" toKeyPath:@"sex"]];
    [humanMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"age" toKeyPath:@"age"]];
    [humanMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"created-at" toKeyPath:@"createdAt"]];
    [humanMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"updated-at" toKeyPath:@"updatedAt"]];
    [humanMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"railsID"]];
    
    RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:_objectManager.managedObjectStore];
    [catMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"]];
    [catMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"nick-name" toKeyPath:@"nickName"]];
    [catMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"birthday" toKeyPath:@"birthday"]];
    [catMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"sex" toKeyPath:@"sex"]];
    [catMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"age" toKeyPath:@"age"]];
    [catMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"created-at" toKeyPath:@"createdAt"]];
    [catMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"updated-at" toKeyPath:@"updatedAt"]];
    [catMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"id" toKeyPath:@"railsID"]];
    
    [catMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"cats" toKeyPath:@"cats" withMapping:catMapping]];
    
    [self.objectManager addResponseDescriptorsFromArray:@[
     [RKResponseDescriptor responseDescriptorWithMapping:humanMapping pathPattern:nil keyPath:@"human" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)],
     [RKResponseDescriptor responseDescriptorWithMapping:humanMapping pathPattern:nil keyPath:@"humans" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]
     ]];
    
    RKObjectMapping *humanSerialization = [RKObjectMapping requestMapping];
    [humanSerialization addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"]];
    [self.objectManager addRequestDescriptor:[RKRequestDescriptor requestDescriptorWithMapping:humanSerialization objectClass:[RKHuman class] rootKeyPath:@"human"]];

    self.humanPOSTRoute = [RKRoute routeWithClass:[RKHuman class] pathPattern:@"/humans" method:RKRequestMethodPOST];
    self.humanGETRoute = [RKRoute routeWithClass:[RKHuman class] pathPattern:@"/humans/:railsID" method:RKRequestMethodGET];
    self.humanDELETERoute = [RKRoute routeWithClass:[RKHuman class] pathPattern:@"/humans/:railsID" method:RKRequestMethodDELETE];
    self.humanCatsRoute = [RKRoute routeWithRelationshipName:@"cats" objectClass:[RKHuman class] pathPattern:@"/humans/:railsID/cats" method:RKRequestMethodGET];
    self.humansCollectionRoute = [RKRoute routeWithName:@"humans" pathPattern:@"/humans" method:RKRequestMethodGET];

    [self.objectManager.router.routeSet addRoute:self.humanPOSTRoute];
    [self.objectManager.router.routeSet addRoute:self.humanGETRoute];
    [self.objectManager.router.routeSet addRoute:self.humanDELETERoute];
    [self.objectManager.router.routeSet addRoute:self.humanCatsRoute];
    [self.objectManager.router.routeSet addRoute:self.humansCollectionRoute];
}

- (void)tearDown
{
    [RKTestFactory tearDown];
}

- (void)testInitializationWithBaseURLSetsDefaultAcceptHeaderValueToJSON
{
    RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
    expect([manager defaultHeaders][@"Accept"]).to.equal(RKMIMETypeJSON);
}

- (void)testInitializationWithBaseURLSetsRequestSerializationMIMETypeToFormURLEncoded
{
    RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
    expect(manager.requestSerializationMIMEType).to.equal(RKMIMETypeFormURLEncoded);
}

- (void)testInitializationWithAFHTTPClientSetsNilAcceptHeaderValue
{
    AFHTTPClient *client = [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
    [client setDefaultHeader:@"Accept" value:@"this/that"];
    RKObjectManager *manager = [[RKObjectManager alloc] initWithHTTPClient:client];
    expect([manager defaultHeaders][@"Accept"]).to.equal(@"this/that");
}

- (void)testDefersToAFHTTPClientParameterEncodingWhenInitializedWithAFHTTPClient
{
    AFHTTPClient *client = [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
    client.parameterEncoding = AFJSONParameterEncoding;
    RKObjectManager *manager = [[RKObjectManager alloc] initWithHTTPClient:client];
    expect([manager requestSerializationMIMEType]).to.equal(RKMIMETypeJSON);
}

- (void)testDefaultsToFormURLEncodingForUnsupportedParameterEncodings
{
    AFHTTPClient *client = [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
    client.parameterEncoding = AFPropertyListParameterEncoding;
    RKObjectManager *manager = [[RKObjectManager alloc] initWithHTTPClient:client];
    expect([manager requestSerializationMIMEType]).to.equal(RKMIMETypeFormURLEncoded);
}

- (void)testShouldUpdateACoreDataBackedTargetObject
{
    NSManagedObjectContext *managedObjectContext = [[RKTestFactory managedObjectStore] persistentStoreManagedObjectContext];
    RKHuman *temporaryHuman = [RKTestFactory insertManagedObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectContext withProperties:nil];
    temporaryHuman.name = @"My Name";
    
    RKManagedObjectRequestOperation *operation = [_objectManager appropriateObjectRequestOperationWithObject:temporaryHuman method:RKRequestMethodPOST path:nil parameters:nil];
    [operation start];
    [operation waitUntilFinished];
    
    expect(operation.mappingResult).notTo.beNil();
    expect([operation.mappingResult array]).notTo.beEmpty();
    RKHuman *human = (RKHuman *)[[operation.mappingResult array] objectAtIndex:0];
    expect(human.objectID).to.equal(temporaryHuman.objectID);
    expect(human.railsID).to.equal(1);
}

- (void)testShouldNotPersistTemporaryEntityToPersistentStoreOnError
{
    NSManagedObjectContext *managedObjectContext = [[RKTestFactory managedObjectStore] persistentStoreManagedObjectContext];
    RKHuman *temporaryHuman = [RKTestFactory insertManagedObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectContext withProperties:nil];
    temporaryHuman.name = @"My Name";
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
    [mapping addAttributeMappingsFromArray:@[@"name"]];
    
    RKManagedObjectRequestOperation *operation = [_objectManager appropriateObjectRequestOperationWithObject:temporaryHuman method:RKRequestMethodPOST path:@"/humans/fail" parameters:nil];
    [operation start];
    [operation waitUntilFinished];
    
    expect([temporaryHuman isNew]).to.equal(YES);
}

- (void)testThatFailedObjectRequestOperationDoesNotSaveObjectToPersistentStore
{
    NSManagedObjectContext *managedObjectContext = [[RKTestFactory managedObjectStore] persistentStoreManagedObjectContext];
    RKHuman *temporaryHuman = [RKTestFactory insertManagedObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectContext withProperties:nil];    
    temporaryHuman.name = @"My Name";
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
    [mapping addAttributeMappingsFromArray:@[@"name"]];
    
    expect([temporaryHuman isNew]).to.equal(YES);
    
    RKManagedObjectRequestOperation *operation = [self.objectManager appropriateObjectRequestOperationWithObject:temporaryHuman method:RKRequestMethodPOST path:@"/humans/fail" parameters:nil];
    [operation start];
    [operation waitUntilFinished];
    
    expect([temporaryHuman isNew]).to.equal(YES);
}

- (void)testShouldDeleteACoreDataBackedTargetObjectOnSuccessfulDeleteReturning200
{
    NSManagedObjectContext *managedObjectContext = [[RKTestFactory managedObjectStore] persistentStoreManagedObjectContext];
    RKHuman *temporaryHuman = [RKTestFactory insertManagedObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectContext withProperties:nil];
    temporaryHuman.name = @"My Name";
    temporaryHuman.railsID = @1;
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
    [mapping addAttributeMappingsFromArray:@[@"name"]];

    // Save it to ensure the object is persisted before we delete it
    [self.objectManager.managedObjectStore.persistentStoreManagedObjectContext save:nil];

    RKManagedObjectRequestOperation *operation = [self.objectManager appropriateObjectRequestOperationWithObject:temporaryHuman method:RKRequestMethodDELETE path:nil parameters:nil];
    [operation start];
    [operation waitUntilFinished];

    NSError *error = nil;
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Human"];
    NSArray *humans = [_objectManager.managedObjectStore.persistentStoreManagedObjectContext executeFetchRequest:fetchRequest error:&error];
    expect(error).to.beNil();
    expect(humans).to.haveCountOf(0);
}

- (void)testShouldDeleteACoreDataBackedTargetObjectOnSuccessfulDeleteReturning204
{
    NSManagedObjectContext *managedObjectContext = [[RKTestFactory managedObjectStore] persistentStoreManagedObjectContext];
    RKHuman *temporaryHuman = [RKTestFactory insertManagedObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectContext withProperties:nil];
    temporaryHuman.name = @"My Name";
    temporaryHuman.railsID = @204;
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
    [mapping addAttributeMappingsFromArray:@[@"name"]];

    // Save it to ensure the object is persisted before we delete it
    [self.objectManager.managedObjectStore.persistentStoreManagedObjectContext save:nil];

    RKManagedObjectRequestOperation *operation = [self.objectManager appropriateObjectRequestOperationWithObject:temporaryHuman method:RKRequestMethodDELETE path:nil parameters:nil];
    [operation start];
    [operation waitUntilFinished];

    NSError *error = nil;
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Human"];
    NSArray *humans = [_objectManager.managedObjectStore.persistentStoreManagedObjectContext executeFetchRequest:fetchRequest error:&error];
    expect(error).to.beNil();
    expect(humans).to.haveCountOf(0);
}

- (void)testCancellationByExactMethodAndPath
{
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/object_manager/cancel" relativeToURL:self.objectManager.HTTPClient.baseURL]];
    RKObjectRequestOperation *operation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:self.objectManager.responseDescriptors];
    [_objectManager enqueueObjectRequestOperation:operation];
    [_objectManager cancelAllObjectRequestOperationsWithMethod:RKRequestMethodGET matchingPathPattern:@"/object_manager/cancel"];
    expect([operation isCancelled]).to.equal(YES);
}

- (void)testCancellationByPathMatch
{
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/object_manager/1234/cancel" relativeToURL:self.objectManager.HTTPClient.baseURL]];
    RKObjectRequestOperation *operation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:self.objectManager.responseDescriptors];
    [_objectManager enqueueObjectRequestOperation:operation];
    [_objectManager cancelAllObjectRequestOperationsWithMethod:RKRequestMethodGET matchingPathPattern:@"/object_manager/:objectID/cancel"];
    expect([operation isCancelled]).to.equal(YES);
}

- (void)testCancellationFailsForMismatchedMethod
{
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/object_manager/cancel" relativeToURL:self.objectManager.HTTPClient.baseURL]];
    RKObjectRequestOperation *operation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:self.objectManager.responseDescriptors];
    [_objectManager enqueueObjectRequestOperation:operation];
    [_objectManager cancelAllObjectRequestOperationsWithMethod:RKRequestMethodPOST matchingPathPattern:@"/object_manager/cancel"];
    expect([operation isCancelled]).to.equal(NO);
}

- (void)testCancellationFailsForMismatchedPath
{
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/object_manager/cancel" relativeToURL:self.objectManager.HTTPClient.baseURL]];
    RKObjectRequestOperation *operation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:self.objectManager.responseDescriptors];
    [_objectManager enqueueObjectRequestOperation:operation];
    [_objectManager cancelAllObjectRequestOperationsWithMethod:RKRequestMethodGET matchingPathPattern:@"/wrong"];
    expect([operation isCancelled]).to.equal(NO);
}

- (void)testCancellationByPathMatchForBaseURLWithPath
{
    self.objectManager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://localhost:4567/object_manager/"]];
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://localhost:4567/object_manager/1234/cancel"]];
    RKObjectRequestOperation *operation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:self.objectManager.responseDescriptors];
    [_objectManager enqueueObjectRequestOperation:operation];
    [_objectManager cancelAllObjectRequestOperationsWithMethod:RKRequestMethodGET matchingPathPattern:@":objectID/cancel"];
    expect([operation isCancelled]).to.equal(YES);
}

- (void)testShouldProperlyFireABatchOfOperations
{
    NSManagedObjectContext *managedObjectContext = [[RKTestFactory managedObjectStore] persistentStoreManagedObjectContext];
    RKHuman *temporaryHuman = [RKTestFactory insertManagedObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectContext withProperties:nil];
    temporaryHuman.name = @"My Name";

    RKManagedObjectRequestOperation *successfulGETOperation = [_objectManager appropriateObjectRequestOperationWithObject:temporaryHuman method:RKRequestMethodGET path:nil parameters:nil];
    RKManagedObjectRequestOperation *successfulPOSTOperation = [_objectManager appropriateObjectRequestOperationWithObject:temporaryHuman method:RKRequestMethodPOST path:nil parameters:nil];
    RKManagedObjectRequestOperation *failedPOSTOperation = [_objectManager appropriateObjectRequestOperationWithObject:temporaryHuman method:RKRequestMethodPOST path:@"/humans/fail" parameters:nil];

    __block NSUInteger progressCallbackCount = 0;
    __block NSUInteger completionBlockOperationCount = 0;
    [_objectManager enqueueBatchOfObjectRequestOperations:@[successfulGETOperation, successfulPOSTOperation, failedPOSTOperation] progress:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
        progressCallbackCount++;
    } completion:^(NSArray *operations) {
        completionBlockOperationCount = operations.count;
    }];
    expect(_objectManager.operationQueue).notTo.beNil();
    [_objectManager.operationQueue waitUntilAllOperationsAreFinished];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        expect(progressCallbackCount).to.equal(3);
        expect(completionBlockOperationCount).to.equal(3);
    });
}

- (void)testShouldProperlyFireABatchOfOperationsFromRoute
{
    NSManagedObjectContext *managedObjectContext = [[RKTestFactory managedObjectStore] persistentStoreManagedObjectContext];
    RKHuman *dan = [RKTestFactory insertManagedObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectContext withProperties:nil];
    dan.name = @"Dan";

    RKHuman *blake = [RKTestFactory insertManagedObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectContext withProperties:nil];
    blake.name = @"Blake";

    RKHuman *jeff = [RKTestFactory insertManagedObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectContext withProperties:nil];
    jeff.name = @"Jeff";

    __block NSUInteger progressCallbackCount = 0;
    __block NSUInteger completionBlockOperationCount = 0;
    [_objectManager enqueueBatchOfObjectRequestOperationsWithRoute:self.humanPOSTRoute objects:@[dan, blake, jeff] progress:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
        progressCallbackCount++;
    } completion:^(NSArray *operations) {
        completionBlockOperationCount = operations.count;
    }];
    expect(_objectManager.operationQueue).notTo.beNil();
    [_objectManager.operationQueue waitUntilAllOperationsAreFinished];

    dispatch_async(dispatch_get_main_queue(), ^{
        expect(progressCallbackCount).to.equal(3);
        expect(completionBlockOperationCount).to.equal(3);
    });
}

- (void)testThatObjectParametersAreNotSentDuringGetObject
{
    RKHuman *temporaryHuman = [RKTestFactory insertManagedObjectForEntityForName:@"Human" inManagedObjectContext:nil withProperties:nil];
    temporaryHuman.name = @"My Name";
    temporaryHuman.railsID = @204;
    RKManagedObjectRequestOperation *operation = [_objectManager appropriateObjectRequestOperationWithObject:temporaryHuman method:RKRequestMethodGET path:nil parameters:@{@"this": @"that"}];
    expect([operation.HTTPRequestOperation.request.URL absoluteString]).to.equal(@"http://127.0.0.1:4567/humans/204?this=that");
}

- (void)testThatObjectParametersAreNotSentDuringDeleteObject
{
    RKHuman *temporaryHuman = [RKTestFactory insertManagedObjectForEntityForName:@"Human" inManagedObjectContext:nil withProperties:nil];
    temporaryHuman.name = @"My Name";
    temporaryHuman.railsID = @204;
    RKManagedObjectRequestOperation *operation = [_objectManager appropriateObjectRequestOperationWithObject:temporaryHuman method:RKRequestMethodDELETE path:nil parameters:@{@"this": @"that"}];
    expect([operation.HTTPRequestOperation.request.URL absoluteString]).to.equal(@"http://127.0.0.1:4567/humans/204?this=that");
}

- (void)testInitializationOfObjectRequestOperationProducesCorrectURLRequest
{
    RKHuman *temporaryHuman = [RKTestFactory insertManagedObjectForEntityForName:@"Human" inManagedObjectContext:nil withProperties:nil];
    NSURLRequest *request = [_objectManager requestWithObject:temporaryHuman method:RKRequestMethodPATCH path:@"/the/path" parameters:@{@"key": @"value"}];
    expect([request.URL absoluteString]).to.equal(@"http://127.0.0.1:4567/the/path");
    expect(request.HTTPMethod).to.equal(@"PATCH");
    expect(request.HTTPBody).notTo.beNil();
    NSString *string = [[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding];
    expect(string).to.equal(@"key=value");
}

- (void)testAFHTTPClientCanModifyRequestsBuiltByObjectManager
{
    RKTestAFHTTPClient *testClient = [[RKTestAFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:@"http://test.com"]];
    RKObjectManager *manager = [[RKObjectManager alloc] initWithHTTPClient:testClient];
    RKHuman *temporaryHuman = [RKTestFactory insertManagedObjectForEntityForName:@"Human" inManagedObjectContext:nil withProperties:nil];
    NSURLRequest *request = [manager requestWithObject:temporaryHuman method:RKRequestMethodPATCH path:@"/the/path" parameters:@{@"key": @"value"}];
    
    expect([request.URL absoluteString]).to.equal(@"http://test.com/the/path");
    expect(request.HTTPMethod).to.equal(@"PATCH");
    expect([request allHTTPHeaderFields][@"test"]).to.equal(@"value");
    expect([request allHTTPHeaderFields][@"Accept"]).to.equal(@"text/html");
}

- (void)testRegistrationOfHTTPRequestOperationClass
{
    RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
    [manager registerRequestOperationClass:[RKTestHTTPRequestOperation class]];
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/test" relativeToURL:manager.baseURL]];
    RKObjectRequestOperation *operation = [manager objectRequestOperationWithRequest:request success:nil failure:nil];
    expect(operation.HTTPRequestOperation).to.beKindOf([RKTestHTTPRequestOperation class]);
}

- (void)testSettingNilHTTPRequestOperationClassRestoresDefaultHTTPOperationClass
{
    RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
    [manager registerRequestOperationClass:[RKTestHTTPRequestOperation class]];
    [manager unregisterRequestOperationClass:[RKTestHTTPRequestOperation class]];
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/test" relativeToURL:manager.baseURL]];
    RKObjectRequestOperation *operation = [manager objectRequestOperationWithRequest:request success:nil failure:nil];
    expect(operation.HTTPRequestOperation).to.beKindOf([RKHTTPRequestOperation class]);
}

- (void)testThatManagedObjectRequestOperationsDefaultToSavingToPersistentStore
{
    RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
    NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/whatever" relativeToURL:manager.baseURL]];
    RKManagedObjectRequestOperation *operation = [manager managedObjectRequestOperationWithRequest:request managedObjectContext:managedObjectContext success:nil failure:nil];
    expect(operation.savesToPersistentStore).to.equal(YES);
}

- (void)testShouldLoadAHuman
{
    __block RKObjectRequestOperation *requestOperation = nil;
    [self.objectManager getObjectsAtPath:@"/JSON/humans/1.json" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
        requestOperation = operation;
    } failure:nil];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        expect(requestOperation.error).to.beNil();
        expect([requestOperation.mappingResult array]).notTo.beEmpty();
        RKHuman *blake = (RKHuman *)[requestOperation.mappingResult array][0];
        expect(blake.name).to.equal(@"Blake Watters");
    });
}

- (void)testShouldLoadAllHumans
{
    __block RKObjectRequestOperation *requestOperation = nil;
    [_objectManager getObjectsAtPath:@"/JSON/humans/all.json" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
        requestOperation = operation;
    } failure:nil];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        NSArray *humans = [requestOperation.mappingResult array];
        expect(humans).to.haveCountOf(2);
        expect(humans[0]).to.beInstanceOf([RKHuman class]);
    });
}

- (void)testThatAttemptingToAddARequestDescriptorThatOverlapsAnExistingEntryGeneratesAnError
{
    RKObjectMapping *mapping = [RKObjectMapping requestMapping];
    RKRequestDescriptor *requestDesriptor1 = [RKRequestDescriptor requestDescriptorWithMapping:mapping objectClass:[RKCat class] rootKeyPath:nil];
    RKRequestDescriptor *requestDesriptor2 = [RKRequestDescriptor requestDescriptorWithMapping:mapping objectClass:[RKCat class] rootKeyPath:@"cat"];
    RKObjectManager *objectManager = [RKTestFactory objectManager];
    [objectManager addRequestDescriptor:requestDesriptor1];
    
    NSException *caughtException = nil;
    @try {
        [objectManager addRequestDescriptor:requestDesriptor2];
    }
    @catch (NSException *exception) {
        caughtException = exception;
    }
    @finally {
        expect(caughtException).notTo.beNil();
    }
}

- (void)testThatRegisteringARequestDescriptorForASubclassSecondWillMatchAppropriately
{
    RKObjectMapping *mapping1 = [RKObjectMapping requestMapping];
    [mapping1 addAttributeMappingsFromArray:@[ @"name" ]];
    RKObjectMapping *mapping2 = [RKObjectMapping requestMapping];
    [mapping2 addAttributeMappingsFromArray:@[ @"age" ]];
    
    RKRequestDescriptor *requestDesriptor1 = [RKRequestDescriptor requestDescriptorWithMapping:mapping1 objectClass:[RKObjectMapperTestModel class] rootKeyPath:nil];
    RKRequestDescriptor *requestDesriptor2 = [RKRequestDescriptor requestDescriptorWithMapping:mapping2 objectClass:[RKSubclassedTestModel class] rootKeyPath:@"subclassed"];
    RKObjectManager *objectManager = [RKTestFactory objectManager];
    objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
    [objectManager addRequestDescriptor:requestDesriptor1];
    [objectManager addRequestDescriptor:requestDesriptor2];
    
    RKSubclassedTestModel *model = [RKSubclassedTestModel new];
    model.name = @"Blake";
    model.age = @30;
    NSURLRequest *request = [objectManager requestWithObject:model method:RKRequestMethodPOST path:@"/path" parameters:nil];
    NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:request.HTTPBody options:0 error:nil];
    expect(dictionary).to.equal(@{ @"subclassed": @{ @"age": @(30) } });
}

- (void)testThatResponseDescriptorWithUnmanagedMappingTriggersCreationOfObjectRequestOperation
{
    RKObjectMapping *vanillaMapping = [RKObjectMapping requestMapping];
    RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:vanillaMapping pathPattern:nil keyPath:nil statusCodes:nil];
    RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
    manager.managedObjectStore = [RKTestFactory managedObjectStore];
    [manager addResponseDescriptor:responseDescriptor];
    RKObjectRequestOperation *objectRequestOperation = [manager appropriateObjectRequestOperationWithObject:nil method:RKRequestMethodGET path:@"/something" parameters:nil];
    expect(objectRequestOperation).to.beInstanceOf([RKObjectRequestOperation class]);
}

- (void)testThatResponseDescriptorWithDynamicMappingContainingEntityMappingsTriggersCreationOfManagedObjectRequestOperation
{
    RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:_objectManager.managedObjectStore];
    RKDynamicMapping *dynamicMapping = [RKDynamicMapping new];
    [dynamicMapping addMatcher:[RKObjectMappingMatcher matcherWithKeyPath:@"whatever" expectedValue:@"whatever" objectMapping:humanMapping]];
    RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:dynamicMapping pathPattern:nil keyPath:nil statusCodes:nil];
    RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
    manager.managedObjectStore = [RKTestFactory managedObjectStore];
    [manager addResponseDescriptor:responseDescriptor];
    RKObjectRequestOperation *objectRequestOperation = [manager appropriateObjectRequestOperationWithObject:nil method:RKRequestMethodGET path:@"/something" parameters:nil];
    expect(objectRequestOperation).to.beInstanceOf([RKManagedObjectRequestOperation class]);
}

- (void)testThatResponseDescriptorWithDynamicMappingUsingABlockTriggersCreationOfManagedObjectRequestOperation
{
    RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:_objectManager.managedObjectStore];
    RKDynamicMapping *dynamicMapping = [RKDynamicMapping new];
    [dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) {
        return humanMapping;
    }];
    RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:dynamicMapping pathPattern:nil keyPath:nil statusCodes:nil];
    RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
    manager.managedObjectStore = [RKTestFactory managedObjectStore];
    [manager addResponseDescriptor:responseDescriptor];
    RKObjectRequestOperation *objectRequestOperation = [manager appropriateObjectRequestOperationWithObject:nil method:RKRequestMethodGET path:@"/something" parameters:nil];
    expect(objectRequestOperation).to.beInstanceOf([RKManagedObjectRequestOperation class]);
}

- (void)testThatResponseDescriptorWithUnmanagedMappingContainingRelationshipMappingWithEntityMappingsTriggersCreationOfManagedObjectRequestOperation
{
    RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:_objectManager.managedObjectStore];
    RKObjectMapping *objectMapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
    [objectMapping addRelationshipMappingWithSourceKeyPath:@"relationship" mapping:humanMapping];
    RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:objectMapping pathPattern:nil keyPath:nil statusCodes:nil];
    RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
    manager.managedObjectStore = [RKTestFactory managedObjectStore];
    [manager addResponseDescriptor:responseDescriptor];
    RKObjectRequestOperation *objectRequestOperation = [manager appropriateObjectRequestOperationWithObject:nil method:RKRequestMethodGET path:@"/something" parameters:nil];
    expect(objectRequestOperation).to.beInstanceOf([RKManagedObjectRequestOperation class]);
}

- (void)testThatResponseDescriptorWithUnmanagedMappingContainingRelationshipMappingWithEntityMappingsDeepWithinObjectGraphTriggersCreationOfManagedObjectRequestOperation
{
    RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:_objectManager.managedObjectStore];
    RKObjectMapping *objectMapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
    [objectMapping addRelationshipMappingWithSourceKeyPath:@"relationship" mapping:humanMapping];
    RKObjectMapping *objectMapping2 = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
    [objectMapping2 addRelationshipMappingWithSourceKeyPath:@"relationship" mapping:objectMapping];
    RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:objectMapping2 pathPattern:nil keyPath:nil statusCodes:nil];
    RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
    manager.managedObjectStore = [RKTestFactory managedObjectStore];
    [manager addResponseDescriptor:responseDescriptor];
    RKObjectRequestOperation *objectRequestOperation = [manager appropriateObjectRequestOperationWithObject:nil method:RKRequestMethodGET path:@"/something" parameters:nil];
    expect(objectRequestOperation).to.beInstanceOf([RKManagedObjectRequestOperation class]);
}

- (void)testChangingHTTPClient
{
    RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
    manager.HTTPClient = [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:@"http://google.com/"]];
    expect([manager.baseURL absoluteString]).to.equal(@"http://google.com/");
}

- (void)testPostingOneObjectAndGettingResponseMatchingAnotherClass
{
    RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[RKTestFactory baseURL]];
    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [userMapping addAttributeMappingsFromDictionary:@{ @"fullname": @"name" }];
    RKObjectMapping *metaMapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
    [metaMapping addAttributeMappingsFromArray:@[ @"status", @"version" ]];
    RKResponseDescriptor *metaResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:metaMapping pathPattern:nil keyPath:@"meta" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
    
    [manager addResponseDescriptorsFromArray:@[ metaResponseDescriptor ]];
    RKTestUser *user = [RKTestUser new];
    RKObjectRequestOperation *requestOperation = [manager appropriateObjectRequestOperationWithObject:user method:RKRequestMethodPOST path:@"/ComplexUser" parameters:nil];
    [requestOperation start];
    [requestOperation waitUntilFinished];
    
    expect(requestOperation.error).to.beNil();
    expect(requestOperation.mappingResult).notTo.beNil();
    expect([requestOperation.mappingResult array]).to.haveCountOf(1);
    NSDictionary *expectedObject = @{ @"status": @"ok", @"version": @"0.3" };
    expect([requestOperation.mappingResult firstObject]).to.equal(expectedObject);
}

- (void)testPostingOneObjectAndGettingResponseMatchingMultipleDescriptors
{
    RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[RKTestFactory baseURL]];
    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [userMapping addAttributeMappingsFromDictionary:@{ @"fullname": @"name" }];
    RKResponseDescriptor *userResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:userMapping pathPattern:nil keyPath:@"data.STUser" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
    RKObjectMapping *metaMapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
    [metaMapping addAttributeMappingsFromArray:@[ @"status", @"version" ]];    
    RKResponseDescriptor *metaResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:metaMapping pathPattern:nil keyPath:@"meta" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
    
    [manager addResponseDescriptorsFromArray:@[ userResponseDescriptor, metaResponseDescriptor ]];
    RKTestUser *user = [RKTestUser new];
    RKObjectRequestOperation *requestOperation = [manager appropriateObjectRequestOperationWithObject:user method:RKRequestMethodPOST path:@"/ComplexUser" parameters:nil];
    [requestOperation start];
    [requestOperation waitUntilFinished];
    
    expect(requestOperation.mappingResult).notTo.beNil();
    expect([requestOperation.mappingResult array]).to.haveCountOf(2);
}

- (void)testThatAppropriateObjectRequestOperationReturnsManagedObjectRequestOperationForManagedObjectWithNoResponseDescriptors
{
    RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
    manager.managedObjectStore = [RKTestFactory managedObjectStore];
    NSManagedObject *managedObject = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:manager.managedObjectStore.mainQueueManagedObjectContext];
    RKObjectRequestOperation *objectRequestOperation = [manager appropriateObjectRequestOperationWithObject:managedObject method:RKRequestMethodPOST path:@"/something" parameters:nil];
    expect(objectRequestOperation).to.beInstanceOf([RKManagedObjectRequestOperation class]);
}

- (void)testCreatingAnObjectRequestWithoutARequestDescriptorButWithParametersSetsTheRequestBody
{
    RKTestUser *user = [RKTestUser new];
    user.name = @"Blake";
    user.emailAddress = @"blake@restkit.org";
    
    RKObjectManager *objectManager = [RKTestFactory objectManager];
    objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
    
    NSURLRequest *request = [objectManager requestWithObject:user method:RKRequestMethodPOST path:@"/path" parameters:@{ @"this": @"that" }];
    id body = [NSJSONSerialization JSONObjectWithData:request.HTTPBody options:0 error:nil];
    NSDictionary *expected = @{ @"this": @"that" };
    expect(body).to.equal(expected);
}

- (void)testPostingAnArrayOfObjectsWhereNoneHaveARootKeyPath
{
    RKObjectMapping *firstRequestMapping = [RKObjectMapping requestMapping];
    [firstRequestMapping addAttributeMappingsFromArray:@[ @"name", @"emailAddress" ]];
    RKObjectMapping *secondRequestMapping = [RKObjectMapping requestMapping];
    [secondRequestMapping addAttributeMappingsFromArray:@[ @"city", @"state" ]];

    RKRequestDescriptor *firstRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:firstRequestMapping objectClass:[RKTestUser class] rootKeyPath:nil];
    RKRequestDescriptor *secondRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:secondRequestMapping objectClass:[RKTestAddress class] rootKeyPath:nil];

    RKTestUser *user = [RKTestUser new];
    user.name = @"Blake";
    user.emailAddress = @"blake@restkit.org";

    RKTestAddress *address = [RKTestAddress new];
    address.city = @"New York City";
    address.state = @"New York";

    RKObjectManager *objectManager = [RKTestFactory objectManager];
    objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
    [objectManager addRequestDescriptor:firstRequestDescriptor];
    [objectManager addRequestDescriptor:secondRequestDescriptor];

    NSArray *arrayOfObjects = @[ user, address ];
    NSURLRequest *request = [objectManager requestWithObject:arrayOfObjects method:RKRequestMethodPOST path:@"/path" parameters:nil];
    NSArray *array = [NSJSONSerialization JSONObjectWithData:request.HTTPBody options:0 error:nil];
    NSArray *expected = @[ @{ @"name": @"Blake", @"emailAddress": @"blake@restkit.org" }, @{ @"city": @"New York City", @"state": @"New York" } ];
    expect(array).to.equal(expected);
}

- (void)testPostingAnArrayOfObjectsWhereAllObjectsHaveAnOverlappingRootKeyPath
{
    RKObjectMapping *firstRequestMapping = [RKObjectMapping requestMapping];
    [firstRequestMapping addAttributeMappingsFromArray:@[ @"name", @"emailAddress" ]];
    RKObjectMapping *secondRequestMapping = [RKObjectMapping requestMapping];
    [secondRequestMapping addAttributeMappingsFromArray:@[ @"city", @"state" ]];

    RKRequestDescriptor *firstRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:firstRequestMapping objectClass:[RKTestUser class] rootKeyPath:@"whatever"];
    RKRequestDescriptor *secondRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:secondRequestMapping objectClass:[RKTestAddress class] rootKeyPath:@"whatever"];

    RKTestUser *user = [RKTestUser new];
    user.name = @"Blake";
    user.emailAddress = @"blake@restkit.org";

    RKTestAddress *address = [RKTestAddress new];
    address.city = @"New York City";
    address.state = @"New York";

    RKObjectManager *objectManager = [RKTestFactory objectManager];
    objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
    [objectManager addRequestDescriptor:firstRequestDescriptor];
    [objectManager addRequestDescriptor:secondRequestDescriptor];

    NSArray *arrayOfObjects = @[ user, address ];
    NSURLRequest *request = [objectManager requestWithObject:arrayOfObjects method:RKRequestMethodPOST path:@"/path" parameters:nil];
    NSArray *array = [NSJSONSerialization JSONObjectWithData:request.HTTPBody options:0 error:nil];
    NSDictionary *expected = @{ @"whatever": @[ @{ @"name": @"Blake", @"emailAddress": @"blake@restkit.org" }, @{ @"city": @"New York City", @"state": @"New York" } ] };
    expect(array).to.equal(expected);
}

- (void)testPostingAnArrayOfObjectsWithMixedRootKeyPath
{
    RKObjectMapping *firstRequestMapping = [RKObjectMapping requestMapping];
    [firstRequestMapping addAttributeMappingsFromArray:@[ @"name", @"emailAddress" ]];
    RKObjectMapping *secondRequestMapping = [RKObjectMapping requestMapping];
    [secondRequestMapping addAttributeMappingsFromArray:@[ @"city", @"state" ]];

    RKRequestDescriptor *firstRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:firstRequestMapping objectClass:[RKTestUser class] rootKeyPath:@"this"];
    RKRequestDescriptor *secondRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:secondRequestMapping objectClass:[RKTestAddress class] rootKeyPath:@"that"];

    RKTestUser *user = [RKTestUser new];
    user.name = @"Blake";
    user.emailAddress = @"blake@restkit.org";

    RKTestAddress *address = [RKTestAddress new];
    address.city = @"New York City";
    address.state = @"New York";

    RKObjectManager *objectManager = [RKTestFactory objectManager];
    objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
    [objectManager addRequestDescriptor:firstRequestDescriptor];
    [objectManager addRequestDescriptor:secondRequestDescriptor];

    NSArray *arrayOfObjects = @[ user, address ];
    NSURLRequest *request = [objectManager requestWithObject:arrayOfObjects method:RKRequestMethodPOST path:@"/path" parameters:nil];
    NSArray *array = [NSJSONSerialization JSONObjectWithData:request.HTTPBody options:0 error:nil];
    NSDictionary *expected = @{ @"this": @{ @"name": @"Blake", @"emailAddress": @"blake@restkit.org" }, @"that": @{ @"city": @"New York City", @"state": @"New York" } };
    expect(array).to.equal(expected);
}

- (void)testPostingAnArrayOfObjectsWithNonNilRootKeyPathAndExtraParameters
{
    RKObjectMapping *firstRequestMapping = [RKObjectMapping requestMapping];
    [firstRequestMapping addAttributeMappingsFromArray:@[ @"name", @"emailAddress" ]];
    RKObjectMapping *secondRequestMapping = [RKObjectMapping requestMapping];
    [secondRequestMapping addAttributeMappingsFromArray:@[ @"city", @"state" ]];

    RKRequestDescriptor *firstRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:firstRequestMapping objectClass:[RKTestUser class] rootKeyPath:@"this"];
    RKRequestDescriptor *secondRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:secondRequestMapping objectClass:[RKTestAddress class] rootKeyPath:@"that"];

    RKTestUser *user = [RKTestUser new];
    user.name = @"Blake";
    user.emailAddress = @"blake@restkit.org";

    RKTestAddress *address = [RKTestAddress new];
    address.city = @"New York City";
    address.state = @"New York";

    RKObjectManager *objectManager = [RKTestFactory objectManager];
    objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
    [objectManager addRequestDescriptor:firstRequestDescriptor];
    [objectManager addRequestDescriptor:secondRequestDescriptor];

    NSArray *arrayOfObjects = @[ user, address ];
    NSURLRequest *request = [objectManager requestWithObject:arrayOfObjects method:RKRequestMethodPOST path:@"/path" parameters:@{ @"extra": @"info" }];
    NSArray *array = [NSJSONSerialization JSONObjectWithData:request.HTTPBody options:0 error:nil];
    NSDictionary *expected = @{ @"this": @{ @"name": @"Blake", @"emailAddress": @"blake@restkit.org" }, @"that": @{ @"city": @"New York City", @"state": @"New York" }, @"extra": @"info" };
    expect(array).to.equal(expected);
}

- (void)testPostingAnArrayWithSingleObjectGeneratesAnArray
{
    RKObjectMapping *firstRequestMapping = [RKObjectMapping requestMapping];
    [firstRequestMapping addAttributeMappingsFromArray:@[ @"name", @"emailAddress" ]];
    
    RKRequestDescriptor *firstRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:firstRequestMapping objectClass:[RKTestUser class] rootKeyPath:@"whatever"];
    
    RKTestUser *user = [RKTestUser new];
    user.name = @"Blake";
    user.emailAddress = @"blake@restkit.org";
    
    RKObjectManager *objectManager = [RKTestFactory objectManager];
    objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
    [objectManager addRequestDescriptor:firstRequestDescriptor];
    
    NSArray *arrayOfObjects = @[ user ];
    NSURLRequest *request = [objectManager requestWithObject:arrayOfObjects method:RKRequestMethodPOST path:@"/path" parameters:nil];
    NSArray *array = [NSJSONSerialization JSONObjectWithData:request.HTTPBody options:0 error:nil];
    NSDictionary *expected = @{ @"whatever": @[ @{ @"name": @"Blake", @"emailAddress": @"blake@restkit.org" } ] };
    expect(array).to.equal(expected);
}

- (void)testPostingNilObjectWithExtraParameters
{
    RKObjectMapping *firstRequestMapping = [RKObjectMapping requestMapping];
    [firstRequestMapping addAttributeMappingsFromArray:@[ @"name", @"emailAddress" ]];
    RKObjectMapping *secondRequestMapping = [RKObjectMapping requestMapping];
    [secondRequestMapping addAttributeMappingsFromArray:@[ @"city", @"state" ]];

    RKRequestDescriptor *firstRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:firstRequestMapping objectClass:[RKTestUser class] rootKeyPath:@"this"];
    RKRequestDescriptor *secondRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:secondRequestMapping objectClass:[RKTestAddress class] rootKeyPath:@"that"];

    RKObjectManager *objectManager = [RKTestFactory objectManager];
    objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
    [objectManager addRequestDescriptor:firstRequestDescriptor];
    [objectManager addRequestDescriptor:secondRequestDescriptor];

    NSDictionary *parameters = @{ @"this": @"that" };
    NSURLRequest *request = [objectManager requestWithObject:nil method:RKRequestMethodPOST path:@"/path" parameters:parameters];
    NSArray *array = [NSJSONSerialization JSONObjectWithData:request.HTTPBody options:0 error:nil];
    expect(array).to.equal(parameters);
}

- (void)testAttemptingToPostAnArrayOfObjectsWithMixtureOfNilAndNonNilRootKeyPathsRaisesError
{
    RKObjectMapping *firstRequestMapping = [RKObjectMapping requestMapping];
    [firstRequestMapping addAttributeMappingsFromArray:@[ @"name", @"emailAddress" ]];
    RKObjectMapping *secondRequestMapping = [RKObjectMapping requestMapping];
    [secondRequestMapping addAttributeMappingsFromArray:@[ @"city", @"state" ]];

    RKRequestDescriptor *firstRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:firstRequestMapping objectClass:[RKTestUser class] rootKeyPath:nil];
    RKRequestDescriptor *secondRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:secondRequestMapping objectClass:[RKTestAddress class] rootKeyPath:nil];

    RKTestUser *user = [RKTestUser new];
    user.name = @"Blake";
    user.emailAddress = @"blake@restkit.org";

    RKTestAddress *address = [RKTestAddress new];
    address.city = @"New York City";
    address.state = @"New York";

    RKObjectManager *objectManager = [RKTestFactory objectManager];
    objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
    [objectManager addRequestDescriptor:firstRequestDescriptor];
    [objectManager addRequestDescriptor:secondRequestDescriptor];

    NSArray *arrayOfObjects = @[ user, address ];
    NSException *caughtException = nil;
    @try {
        NSURLRequest __unused *request = [objectManager requestWithObject:arrayOfObjects method:RKRequestMethodPOST path:@"/path" parameters:@{ @"name": @"Foo" }];
    }
    @catch (NSException *exception) {
        caughtException = exception;
        expect([exception name]).to.equal(NSInvalidArgumentException);
        expect([exception reason]).to.equal(@"Cannot merge parameters with array of object representations serialized with a nil root key path.");
    }
    expect(caughtException).notTo.beNil();
}

- (void)testThatAttemptingToPostObjectsWithAMixtureOfNilAndNonNilRootKeyPathsRaisesError
{
    RKObjectMapping *firstRequestMapping = [RKObjectMapping requestMapping];
    [firstRequestMapping addAttributeMappingsFromArray:@[ @"name", @"emailAddress" ]];
    RKObjectMapping *secondRequestMapping = [RKObjectMapping requestMapping];
    [secondRequestMapping addAttributeMappingsFromArray:@[ @"city", @"state" ]];

    RKRequestDescriptor *firstRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:firstRequestMapping objectClass:[RKTestUser class] rootKeyPath:@"bang"];
    RKRequestDescriptor *secondRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:secondRequestMapping objectClass:[RKTestAddress class] rootKeyPath:nil];

    RKTestUser *user = [RKTestUser new];
    user.name = @"Blake";
    user.emailAddress = @"blake@restkit.org";

    RKTestAddress *address = [RKTestAddress new];
    address.city = @"New York City";
    address.state = @"New York";

    RKObjectManager *objectManager = [RKTestFactory objectManager];
    objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
    [objectManager addRequestDescriptor:firstRequestDescriptor];
    [objectManager addRequestDescriptor:secondRequestDescriptor];

    NSArray *arrayOfObjects = @[ user, address ];
    NSException *caughtException = nil;
    @try {
        NSURLRequest __unused *request = [objectManager requestWithObject:arrayOfObjects method:RKRequestMethodPOST path:@"/path" parameters:nil];
    }
    @catch (NSException *exception) {
        caughtException = exception;
        expect([exception name]).to.equal(NSInvalidArgumentException);
        expect([exception reason]).to.equal(@"Invalid request descriptor configuration: The request descriptors specify that multiple objects be serialized at incompatible key paths. Cannot serialize objects at the `nil` root key path in the same request as objects with a non-nil root key path. Please check your request descriptors and try again.");
    }
    expect(caughtException).notTo.beNil();
}

#pragma mark - Object Request Operation Registration

- (void)testRegistrationOfObjectRequestOperationClass
{
    RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
    [manager registerRequestOperationClass:[RKTestObjectRequestOperation class]];
    NSURL *URL = [NSURL URLWithString:@"/match" relativeToURL:manager.baseURL];
    NSURLRequest *request = [NSURLRequest requestWithURL:URL];
    RKObjectRequestOperation *operation = [manager objectRequestOperationWithRequest:request success:nil failure:nil];
    expect(operation).to.beInstanceOf([RKTestObjectRequestOperation class]);
}

- (void)testRegistrationOfObjectRequestOperationClassRespectsSubclassDecisionToProcessRequest
{
    RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
    [manager registerRequestOperationClass:[RKTestObjectRequestOperation class]];
    NSURL *URL = [NSURL URLWithString:@"/mismatch" relativeToURL:manager.baseURL];
    NSURLRequest *request = [NSURLRequest requestWithURL:URL];
    RKObjectRequestOperation *operation = [manager objectRequestOperationWithRequest:request success:nil failure:nil];
    expect(operation).notTo.beInstanceOf([RKTestObjectRequestOperation class]);
    expect(operation).to.beInstanceOf([RKObjectRequestOperation class]);
}

- (void)testRegistrationOfManagedObjectRequestOperationClass
{
    RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
    RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
    [manager registerRequestOperationClass:[RKTestManagedObjectRequestOperation class]];
    NSURL *URL = [NSURL URLWithString:@"/match" relativeToURL:manager.baseURL];
    NSURLRequest *request = [NSURLRequest requestWithURL:URL];
    RKObjectRequestOperation *operation = [manager managedObjectRequestOperationWithRequest:request managedObjectContext:managedObjectStore.mainQueueManagedObjectContext success:nil failure:nil];
    expect(operation).to.beInstanceOf([RKTestManagedObjectRequestOperation class]);
}

- (void)testRegistrationOfManagedObjectRequestOperationClassRespectsSubclassDecisionToProcessRequest
{
    RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
    RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
    [manager registerRequestOperationClass:[RKTestManagedObjectRequestOperation class]];
    NSURL *URL = [NSURL URLWithString:@"/mismatch" relativeToURL:manager.baseURL];
    NSURLRequest *request = [NSURLRequest requestWithURL:URL];
    RKObjectRequestOperation *operation = [manager managedObjectRequestOperationWithRequest:request managedObjectContext:managedObjectStore.mainQueueManagedObjectContext success:nil failure:nil];
    expect(operation).notTo.beInstanceOf([RKTestManagedObjectRequestOperation class]);
    expect(operation).to.beInstanceOf([RKManagedObjectRequestOperation class]);
}

- (void)testThatPostingUnsavedObjectWithUnsavedChildrenDoesNotCrash
{
    RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
    NSManagedObject *developmentTag = [NSEntityDescription insertNewObjectForEntityForName:@"Tag" inManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext];
    [developmentTag setValue:@"development" forKey:@"name"];
    NSManagedObject *restkitTag = [NSEntityDescription insertNewObjectForEntityForName:@"Tag" inManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext];
    [restkitTag setValue:@"restkit" forKey:@"name"];

    NSManagedObject *post = [NSEntityDescription insertNewObjectForEntityForName:@"Post" inManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext];
    [post setValue:@"Post Title" forKey:@"title"];
    [post setValue:[NSSet setWithObjects:developmentTag, restkitTag, nil]  forKey:@"tags"];

    RKEntityMapping *postMapping = [RKEntityMapping mappingForEntityForName:@"Post" inManagedObjectStore:managedObjectStore];
    postMapping.identificationAttributes = @[ @"title" ];
    [postMapping addAttributeMappingsFromArray:@[ @"title", @"body" ]];
    RKEntityMapping *tagMapping = [RKEntityMapping mappingForEntityForName:@"Tag" inManagedObjectStore:managedObjectStore];
    tagMapping.identificationAttributes = @[ @"name" ];
    [tagMapping addAttributeMappingsFromArray:@[ @"name" ]];
    [postMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"tags" toKeyPath:@"tags" withMapping:tagMapping]];
    RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:postMapping pathPattern:nil keyPath:@"post" statusCodes:[NSIndexSet indexSetWithIndex:200]];

    RKObjectMapping *tagRequestMapping = [RKObjectMapping requestMapping];
    [tagRequestMapping addAttributeMappingsFromArray:@[ @"name" ]];
    RKObjectMapping *postRequestMapping = [RKObjectMapping requestMapping];
    [postRequestMapping addAttributeMappingsFromArray:@[ @"title", @"body" ]];
    [postRequestMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"tags" toKeyPath:@"tags" withMapping:tagRequestMapping]];
    RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:postRequestMapping objectClass:[NSManagedObject class] rootKeyPath:nil];

    RKObjectManager *objectManager = [RKTestFactory objectManager];
    objectManager.managedObjectStore = managedObjectStore;
    [objectManager addResponseDescriptor:responseDescriptor];
    [objectManager addRequestDescriptor:requestDescriptor];

    expect([post isNew]).to.equal(YES);
    expect([post.objectID isTemporaryID]).to.equal(YES);
    expect([developmentTag isNew]).to.equal(YES);
    expect([developmentTag.objectID isTemporaryID]).to.equal(YES);
    expect([restkitTag isNew]).to.equal(YES);
    expect([restkitTag.objectID isTemporaryID]).to.equal(YES);

    __block RKMappingResult *postMappingResult = nil;
    RKManagedObjectRequestOperation *operation = [objectManager appropriateObjectRequestOperationWithObject:post method:RKRequestMethodPOST path:@"/posts.json" parameters:nil];
    operation.savesToPersistentStore = NO;
    [operation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
        postMappingResult = mappingResult;
    } failure:nil];
    [objectManager enqueueObjectRequestOperation:operation];

    expect(postMappingResult).willNot.beNil();
    expect([post.objectID isTemporaryID]).will.equal(NO);
    expect([developmentTag.objectID isTemporaryID]).will.equal(NO);
    expect([restkitTag.objectID isTemporaryID]).will.equal(NO);
}

- (void)testPathMatchingForMultipartRequest
{
    RKObjectManager *objectManager = [RKTestFactory objectManager];
    NSString *path = @"/api/upload/";
    
    NSData *blakePng = [RKTestFixture dataWithContentsOfFixture:@"blake.png"];
    NSMutableURLRequest *request = [objectManager multipartFormRequestWithObject:nil method:RKRequestMethodPOST path:path parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
        [formData appendPartWithFileData:blakePng
                                    name:@"file"
                                fileName:@"blake.png"
                                mimeType:@"image/png"];
    }];
    
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [mapping addAttributeMappingsFromArray:@[ @"name" ]];
    RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:mapping pathPattern:path keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
    [objectManager addResponseDescriptor:responseDescriptor];
    
    RKObjectRequestOperation * operation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:@[ responseDescriptor ]];
    [[RKObjectManager sharedManager] enqueueObjectRequestOperation:operation];
    
    expect([operation isFinished]).will.equal(YES);
    expect(operation.error).to.beNil();
    RKTestUser *user = [operation.mappingResult firstObject];
    expect(user.name).to.equal(@"Blake");
}

- (void)testPostingTemporaryObjectThatDoesNotExistInCacheDoesNotCreateDuplicatesWithFetchRequestCache
{
    RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
    RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new];
    managedObjectStore.managedObjectCache = managedObjectCache;
    
    NSEntityDescription *entity = [managedObjectStore.managedObjectModel entitiesByName][@"Human"];
    RKEntityMapping *entityMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore];
    entityMapping.identificationAttributes = @[ @"railsID" ];
    [entityMapping addAttributeMappingsFromDictionary:@{ @"id": @"railsID", @"name": @"name" }];
    
    NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    [childContext setParentContext:managedObjectStore.mainQueueManagedObjectContext];
    RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:childContext];
    
    RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:[RKTestFactory baseURL]];
    objectManager.managedObjectStore = managedObjectStore;
    [objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:entityMapping pathPattern:@"/humans" keyPath:@"human" statusCodes:[NSIndexSet indexSetWithIndex:201]]];
    __block RKMappingResult *mappingResult = nil;
    [objectManager postObject:human path:@"/humans" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *blockMappingResult) {
        mappingResult = blockMappingResult;
    } failure:nil];
    
    expect(mappingResult).willNot.beNil();
    
    expect([[human objectID] isTemporaryID]).to.beFalsy();
    
    NSSet *managedObjects = [managedObjectCache managedObjectsWithEntity:entity attributeValues:@{ @"railsID": @(1) } inManagedObjectContext:childContext];
    expect(managedObjects).to.haveCountOf(1);
    
    [objectManager postObject:human path:@"/humans" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *blockMappingResult) {
        mappingResult = blockMappingResult;
    } failure:nil];
    
    expect(mappingResult).willNot.beNil();
    managedObjects = [managedObjectCache managedObjectsWithEntity:entity attributeValues:@{ @"railsID": @(1) } inManagedObjectContext:childContext];
    expect(managedObjects).to.haveCountOf(1);
}

- (void)testPostingTemporaryObjectWithChildObjectsThatDoesNotExistInCacheDoesNotCreateDuplicatesWithFetchRequestCache
{
    RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
    RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new];
    managedObjectStore.managedObjectCache = managedObjectCache;
    
    NSManagedObject *developmentTag = [NSEntityDescription insertNewObjectForEntityForName:@"Tag" inManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext];
    [developmentTag setValue:@"development" forKey:@"name"];
    NSManagedObject *restkitTag = [NSEntityDescription insertNewObjectForEntityForName:@"Tag" inManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext];
    [restkitTag setValue:@"restkit" forKey:@"name"];
    
    NSManagedObject *post = [NSEntityDescription insertNewObjectForEntityForName:@"Post" inManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext];
    [post setValue:@"Post Title" forKey:@"title"];
    [post setValue:[NSSet setWithObjects:developmentTag, restkitTag, nil]  forKey:@"tags"];
    
    RKEntityMapping *postMapping = [RKEntityMapping mappingForEntityForName:@"Post" inManagedObjectStore:managedObjectStore];
    postMapping.identificationAttributes = @[ @"title" ];
    [postMapping addAttributeMappingsFromArray:@[ @"title", @"body" ]];
    RKEntityMapping *tagMapping = [RKEntityMapping mappingForEntityForName:@"Tag" inManagedObjectStore:managedObjectStore];
    [tagMapping addAttributeMappingsFromArray:@[ @"name" ]];
    RKRelationshipMapping *relationshipMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:@"tags" toKeyPath:@"tags" withMapping:tagMapping];
    [postMapping addPropertyMapping:relationshipMapping];
    RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:postMapping pathPattern:nil keyPath:@"post" statusCodes:[NSIndexSet indexSetWithIndex:200]];
    
    RKObjectMapping *tagRequestMapping = [RKObjectMapping requestMapping];
    [tagRequestMapping addAttributeMappingsFromArray:@[ @"name" ]];
    RKObjectMapping *postRequestMapping = [RKObjectMapping requestMapping];
    [postRequestMapping addAttributeMappingsFromArray:@[ @"title", @"body" ]];
    [postRequestMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"tags" toKeyPath:@"tags" withMapping:tagRequestMapping]];
    RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:postRequestMapping objectClass:[NSManagedObject class] rootKeyPath:nil];
    
    RKObjectManager *objectManager = [RKTestFactory objectManager];
    objectManager.managedObjectStore = managedObjectStore;
    [objectManager addResponseDescriptor:responseDescriptor];
    [objectManager addRequestDescriptor:requestDescriptor];
    
    expect([post isNew]).to.equal(YES);
    expect([post.objectID isTemporaryID]).to.equal(YES);
    expect([developmentTag isNew]).to.equal(YES);
    expect([developmentTag.objectID isTemporaryID]).to.equal(YES);
    expect([restkitTag isNew]).to.equal(YES);
    expect([restkitTag.objectID isTemporaryID]).to.equal(YES);
    
    __block RKMappingResult *postMappingResult = nil;
    RKManagedObjectRequestOperation *operation = [objectManager appropriateObjectRequestOperationWithObject:post method:RKRequestMethodPOST path:@"/posts.json" parameters:nil];
    operation.savesToPersistentStore = NO;
    [operation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
        postMappingResult = mappingResult;
    } failure:nil];
    [objectManager enqueueObjectRequestOperation:operation];
    
    expect(postMappingResult).willNot.beNil();
    expect([post.objectID isTemporaryID]).will.equal(NO);
    expect([developmentTag.objectID isTemporaryID]).will.equal(NO);
    expect([restkitTag.objectID isTemporaryID]).will.equal(NO);
    
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Tag"];
    NSUInteger tagsCount = [objectManager.managedObjectStore.mainQueueManagedObjectContext countForFetchRequest:fetchRequest error:nil];
    expect(tagsCount).to.equal(2);
}

- (void)testThatMappingAToManyRelationshipOnAnExistingSetOfObjectsDoesNotReuseTheFirstObjectInTheCollection
{
    RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
    RKEntityMapping *postMapping = [RKEntityMapping mappingForEntityForName:@"Post" inManagedObjectStore:managedObjectStore];
    [postMapping addAttributeMappingsFromDictionary:@{ @"title": @"title" }];
    RKEntityMapping *tagMapping = [RKEntityMapping mappingForEntityForName:@"Tag" inManagedObjectStore:managedObjectStore];
    [tagMapping addAttributeMappingsFromDictionary:@{ @"name": @"name" }];
    [postMapping addRelationshipMappingWithSourceKeyPath:@"tags" mapping:tagMapping];
    NSDictionary *representation = @{ @"title": @"The Post", @"tags": @[ @{ @"name": @"first" }, @{ @"name": @"second" } ] };
    RKFetchRequestManagedObjectCache *fetchRequestCache = [RKFetchRequestManagedObjectCache new];
    RKManagedObjectMappingOperationDataSource *dataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext cache:fetchRequestCache];
    RKMapperOperation *mapperOperation = [[RKMapperOperation alloc] initWithRepresentation:representation mappingsDictionary:@{ [NSNull null]: postMapping }];
    mapperOperation.mappingOperationDataSource = dataSource;
    NSError *error = nil;
    BOOL success = [mapperOperation execute:&error];
    expect(success).to.beTruthy();
    RKPost *post = [mapperOperation.mappingResult firstObject];
    expect(post.tags).to.haveCountOf(2);
    
    mapperOperation = [[RKMapperOperation alloc] initWithRepresentation:representation mappingsDictionary:@{ [NSNull null]: postMapping }];
    mapperOperation.mappingOperationDataSource = dataSource;
    success = [mapperOperation execute:&error];
    expect(success).to.beTruthy();
    post = [mapperOperation.mappingResult firstObject];
    expect(post.tags).to.haveCountOf(2);
}

- (void)testMappingErrorsFromFiveHundredStatusCodeRange
{
    RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:[RKTestFactory baseURL]];    
    NSIndexSet *statusCodes = RKStatusCodeIndexSetForClass(RKStatusCodeClassServerError);
    RKObjectMapping *errorResponseMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
    [errorResponseMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:@"errorMessage"]];
    [objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:errorResponseMapping pathPattern:nil keyPath:@"errors" statusCodes:statusCodes]];
    
    __block NSError *error = nil;
    [objectManager getObjectsAtPath:@"/fail" parameters:nil success:nil failure:^(RKObjectRequestOperation *operation, NSError *blockError) {
        error = blockError;
    }];
    
    expect(error).willNot.beNil();
    expect([error localizedDescription]).to.equal(@"error1, error2");
}

- (void)testPostingATemporaryObjectThatHasJustBeenSaved
{
    NSManagedObjectContext *managedObjectContext = [[RKTestFactory managedObjectStore] mainQueueManagedObjectContext];
    RKHuman *temporaryHuman = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectContext];
    temporaryHuman.name = @"My Name";
    expect([temporaryHuman.objectID isTemporaryID]).to.equal(YES);
    NSError *error = nil;
    BOOL success = [temporaryHuman.managedObjectContext saveToPersistentStore:&error];
    expect(success).to.equal(YES);
    
    RKManagedObjectRequestOperation *operation = [_objectManager appropriateObjectRequestOperationWithObject:temporaryHuman method:RKRequestMethodPOST path:nil parameters:nil];
    [_objectManager enqueueObjectRequestOperation:operation];
    expect([operation isFinished]).will.equal(YES);
    
    expect(operation.mappingResult).notTo.beNil();
    expect([operation.mappingResult array]).notTo.beEmpty();
    RKHuman *human = (RKHuman *)[[operation.mappingResult array] objectAtIndex:0];
    expect(human.objectID).to.equal(temporaryHuman.objectID);
    expect(human.railsID).to.equal(1);
}

- (void)testConnectingARelationshipToAnObjectUsingRoutingMetadata
{
    RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
    RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new];
    managedObjectStore.managedObjectCache = managedObjectCache;
    
    NSManagedObject *post = [NSEntityDescription insertNewObjectForEntityForName:@"Post" inManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext];
    [post setValue:@"The Post" forKey:@"title"];
    [post setValue:@(1234) forKey:@"postID"];
    
    RKEntityMapping *tagMapping = [RKEntityMapping mappingForEntityForName:@"Tag" inManagedObjectStore:managedObjectStore];
    [tagMapping addAttributeMappingsFromDictionary:@{ @"@metadata.routing.parameters.postID": @"postID", @"name": @"name" }];
    [tagMapping addConnectionForRelationship:@"posts" connectedBy:@{ @"postID": @"postID" }];
    
    RKObjectManager *objectManager = [RKTestFactory objectManager];
    RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:tagMapping pathPattern:@"/posts/:postID/tags" keyPath:nil statusCodes:[NSIndexSet indexSetWithIndex:200]];
    [objectManager addResponseDescriptor:responseDescriptor];
    [objectManager.router.routeSet addRoute:[RKRoute routeWithRelationshipName:@"tags" objectClass:[RKPost class] pathPattern:@"/posts/:postID/tags" method:RKRequestMethodGET]];
    __block RKMappingResult *mappingResult = nil;
    [objectManager getObjectsAtPathForRelationship:@"tags" ofObject:post parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *blockMappingResult) {
        mappingResult = blockMappingResult;
    } failure:nil];
    expect(mappingResult).willNot.beNil();
    NSArray *tags = [mappingResult array];
    expect(tags).notTo.beNil();
    NSArray *tagNames = @[@"development", @"restkit"];
    expect([tags valueForKey:@"name"]).to.equal(tagNames);
    NSSet *connectedTags = [post valueForKey:@"tags"];
    expect(connectedTags).notTo.beEmpty();
}

- (void)testMappingMetadataParameterForNamedRoute
{
    RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:[RKTestFactory baseURL]];
    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [userMapping addAttributeMappingsFromDictionary:@{ @"name": @"name", @"@metadata.routing.parameters.userID": @"position" }];    
    [objectManager.router.routeSet addRoute:[RKRoute routeWithName:@"load_human" pathPattern:@"/JSON/humans/:userID\\.json" method:RKRequestMethodGET]];
    RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:userMapping pathPattern:@"/JSON/humans/:userID\\.json" keyPath:@"human" statusCodes:[NSIndexSet indexSetWithIndex:200]];
    [objectManager addResponseDescriptor:responseDescriptor];
    
    RKTestUser *user = [RKTestUser new];
    user.userID = @1;
    __block RKMappingResult *mappingResult = nil;
    [objectManager getObjectsAtPathForRouteNamed:@"load_human" object:user parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *blockMappingResult) {
        mappingResult = blockMappingResult;
    } failure:nil];
    
    expect(mappingResult).willNot.beNil();
    RKTestUser *anotherUser = [mappingResult firstObject];
    expect(anotherUser).notTo.equal(user);
    expect(anotherUser.name).to.equal(@"Blake Watters");
    expect(anotherUser.position).to.equal(@1);
}

- (void)testRoutingMetadataWithAppropriateObjectRequestOperation
{
    NSManagedObjectContext *managedObjectContext = [[RKTestFactory managedObjectStore] persistentStoreManagedObjectContext];
    RKHuman *temporaryHuman = [RKTestFactory insertManagedObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectContext withProperties:nil];
    temporaryHuman.name = @"My Name";
    temporaryHuman.railsID = @(12345);
    RKManagedObjectRequestOperation *operation = [_objectManager appropriateObjectRequestOperationWithObject:temporaryHuman method:RKRequestMethodDELETE path:nil parameters:nil];
    expect([operation.mappingMetadata valueForKeyPath:@"routing.parameters.railsID"]).to.equal(@"12345");
}

- (void)testThatNoCrashOccursWhenLoadingNamedRouteWithNilObject
{
    RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:[RKTestFactory baseURL]];
    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
    [objectManager.router.routeSet addRoute:[RKRoute routeWithName:@"named_route" pathPattern:@"/JSON/humans/1.json" method:RKRequestMethodGET]];
    RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:userMapping pathPattern:nil keyPath:@"human" statusCodes:[NSIndexSet indexSetWithIndex:200]];
    [objectManager addResponseDescriptor:responseDescriptor];
    
    __block RKMappingResult *mappingResult = nil;
    [objectManager getObjectsAtPathForRouteNamed:@"named_route" object:nil parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *blockMappingResult) {
        mappingResult = blockMappingResult;
    } failure:nil];
    
    expect(mappingResult).willNot.beNil();
}

- (void)testUseOfMetadataMappingAsIdentificationAttribute
{
    RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:[RKTestFactory baseURL]];
    objectManager.managedObjectStore = [RKTestFactory managedObjectStore];
    RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:objectManager.managedObjectStore];
    [humanMapping addAttributeMappingsFromDictionary:@{ @"name": @"name", @"@metadata.routing.parameters.railsID": @"favoriteCatID" }];
    [humanMapping setIdentificationAttributes:@[ @"favoriteCatID" ]];
    [objectManager.router.routeSet addRoute:[RKRoute routeWithName:@"load_human" pathPattern:@"/JSON/humans/:railsID\\.json" method:RKRequestMethodGET]];
    RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:humanMapping pathPattern:@"/JSON/humans/:railsID\\.json" keyPath:@"human" statusCodes:[NSIndexSet indexSetWithIndex:200]];
    [objectManager addResponseDescriptor:responseDescriptor];
    
    RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:objectManager.managedObjectStore.mainQueueManagedObjectContext];
    human.railsID = @1;
    __block RKMappingResult *mappingResult = nil;
    [objectManager getObjectsAtPathForRouteNamed:@"load_human" object:human parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *blockMappingResult) {
        mappingResult = blockMappingResult;
    } failure:nil];
    
    expect(mappingResult).willNot.beNil();
    RKHuman *anotherHuman = [mappingResult firstObject];
    expect(anotherHuman.name).to.equal(@"Blake Watters");
    expect(anotherHuman.favoriteCatID).to.equal(@1);
    expect([anotherHuman isEqual:human]).to.beFalsy();
}

- (void)testThatPostingAnArrayOfObjectsThatWereManuallyCreatedDoesNotResultInTheCreationOfDuplicatedObjects
{
    RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:[RKTestFactory baseURL]];
    RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
    RKInMemoryManagedObjectCache *inMemoryCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext];
    managedObjectStore.managedObjectCache = inMemoryCache;
    objectManager.managedObjectStore = managedObjectStore;
    
    NSManagedObject *developmentTag = [NSEntityDescription insertNewObjectForEntityForName:@"Tag" inManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext];
    [developmentTag setValue:@"development" forKey:@"name"];
    NSManagedObject *restKitTag = [NSEntityDescription insertNewObjectForEntityForName:@"Tag" inManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext];
    [restKitTag setValue:@"restkit" forKey:@"name"];
    [managedObjectStore.mainQueueManagedObjectContext saveToPersistentStore:nil];
    
    RKEntityMapping *tagMapping = [RKEntityMapping mappingForEntityForName:@"Tag" inManagedObjectStore:managedObjectStore];
    tagMapping.identificationAttributes = @[ @"name" ];
    [tagMapping addAttributeMappingsFromArray:@[ @"name" ]];
    RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:tagMapping pathPattern:nil keyPath:nil statusCodes:nil];
    [objectManager addResponseDescriptor:responseDescriptor];
    RKObjectMapping *requestMapping = [RKObjectMapping requestMapping];
    [requestMapping addAttributeMappingsFromArray:@[ @"name" ]];
    RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:requestMapping objectClass:[NSManagedObject class] rootKeyPath:nil];
    [objectManager addRequestDescriptor:requestDescriptor];
    
    __block RKMappingResult *mappingResult = nil;
    NSArray *tags = @[ developmentTag, restKitTag ];
    [objectManager postObject:tags path:@"/tags" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *blockMappingResult) {
        mappingResult = blockMappingResult;
    } failure:nil];
    expect(mappingResult).willNot.beNil();
    NSSet *tagObjectIDs = [NSSet setWithArray:[tags valueForKey:@"objectID"]];
    NSSet *mappedObjectIDs = [NSSet setWithArray:[mappingResult.array valueForKey:@"objectID"]];
    expect(mappedObjectIDs).to.equal(tagObjectIDs);
    NSUInteger tagsCount = [managedObjectStore.mainQueueManagedObjectContext countForEntityForName:@"Tag" predicate:nil error:nil];
    expect(tagsCount).to.equal(2);
};

@end
