static bool acceptApplicants(SOSAccountRef account, CFIndex count) {
    bool retval = false;
    CFErrorRef error = NULL;
    CFArrayRef applicants = SOSAccountCopyApplicants(account, &error);
    ok(applicants && CFArrayGetCount(applicants) == count, "See %ld applicants %@ (%@)", count, applicants, error);
    CFReleaseNull(error);
    require_quiet(CFArrayGetCount(applicants) == count, xit);
    ok((retval=SOSAccountAcceptApplicants(account, applicants, &error)), "Accept applicants into the fold");
    CFReleaseNull(error);
    CFReleaseSafe(applicants);
xit:
    return retval;
}
예제 #2
0
static bool SOSTestAcceptApplicant(SOSAccountRef acceptor, CFIndex appsExpected, CFErrorRef *error) {
    bool retval = true;
    CFArrayRef applicants = SOSAccountCopyApplicants(acceptor, error);
    CFStringRef acceptorName = SOSAccountCopyName(acceptor);
    
    ok(applicants && CFArrayGetCount(applicants) == appsExpected, "See %ld applicant %@ (%@)", appsExpected, applicants, *error);
    if(CFArrayGetCount(applicants) != appsExpected) retval = false;
    ok(retval && (retval = SOSAccountAcceptApplicants(acceptor, applicants, error)), "%@ accepts (%@)", acceptorName, *error);
    CFReleaseNull(*error);
    CFReleaseNull(applicants);
    CFReleaseNull(acceptorName);
    
    return retval;
}
static void tests(void)
{
    CFErrorRef error = NULL;
    CFDataRef cfpassword = CFDataCreate(NULL, (uint8_t *) "FooFooFoo", 10);
    CFStringRef cfaccount = CFSTR("*****@*****.**");
    CFStringRef circle_name = CFSTR("TestSource");
    
    CFMutableDictionaryRef changes = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);

    SOSAccountRef alice_account = CreateAccountForLocalChanges(CFSTR("Alice"), circle_name);
    SOSAccountRef bob_account = CreateAccountForLocalChanges(CFSTR("Bob"), circle_name);
    SOSAccountRef carole_account = CreateAccountForLocalChanges(CFSTR("Carole"), circle_name);
    
    ok(SOSAccountAssertUserCredentialsAndUpdate(bob_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
    
    // Bob wins writing at this point, feed the changes back to alice.
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 1, "updates");

    ok(SOSAccountAssertUserCredentialsAndUpdate(alice_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
    CFReleaseNull(error);
    
    ok(SOSAccountAssertUserCredentialsAndUpdate(carole_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
    CFReleaseNull(error);
    ok(SOSAccountResetToOffering_wTxn(alice_account, &error), "Reset to offering (%@)", error);
    CFReleaseNull(error);

    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 2, "updates");

    ok(SOSAccountJoinCircles_wTxn(bob_account, &error), "Bob Applies (%@)", error);
    CFReleaseNull(error);

    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 2, "updates");

    {
        CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error);
        
        ok(applicants && CFArrayGetCount(applicants) == 1, "See one applicant %@ (%@)", applicants, error);
        ok(SOSAccountAcceptApplicants(alice_account, applicants, &error), "Alice accepts (%@)", error);
        CFReleaseNull(error);
        CFReleaseNull(applicants);
    }
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 3, "updates");

    accounts_agree("bob&alice pair", bob_account, alice_account);
    
    CFArrayRef peers = SOSAccountCopyPeers(alice_account, &error);
    ok(peers && CFArrayGetCount(peers) == 2, "See two peers %@ (%@)", peers, error);
    CFReleaseNull(peers);
    
    SOSFullPeerInfoRef fpiAlice = SOSAccountGetMyFullPeerInfo(alice_account);
    CFStringRef alice_id = CFStringCreateCopy(NULL, SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(fpiAlice)));
    
    ok(SOSAccountLeaveCircle(alice_account, &error), "Alice Leaves (%@)", error);
    CFReleaseNull(error);
    CFReleaseNull(cfpassword);
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 2, "updates");

    accounts_agree("Alice bails", bob_account, alice_account);
    accounts_agree("Alice bails", bob_account, carole_account);
    
    SOSAccountCleanupRetirementTickets(bob_account, 0, &error);

    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 1, "updates");

    //is(CFDictionaryGetCountOfValue(BobChanges, kCFNull),0, "0 Keys Nulled Out");
    
    ok(SOSAccountJoinCircles_wTxn(carole_account, &error), "Carole Applies (%@)", error);
    CFReleaseNull(error);

    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 2, "updates");

    
    {
        CFArrayRef applicants = SOSAccountCopyApplicants(bob_account, &error);
        
        ok(applicants && CFArrayGetCount(applicants) == 1, "See one applicant %@ (%@)", applicants, error);
        ok(SOSAccountAcceptApplicants(bob_account, applicants, &error), "Bob accepts Carole (%@)", error);
        CFReleaseNull(error);
        CFReleaseNull(applicants);
    }
    
    // Bob should not yet cleanup Alice's retirment here on his own since it hasn't been long enough
    // by default.
    //is(CFDictionaryGetCountOfValue(BobChanges, kCFNull),0, "0 Keys Nulled Out");

    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 3, "updates");

    accounts_agree("Carole joins", bob_account, carole_account);
    
    SOSAccountCleanupRetirementTickets(bob_account, 0, &error);

    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 1, "updates");

    is(countPeers(bob_account), 2, "Active peers after forced cleanup");
    is(countActivePeers(bob_account), 3, "Inactive peers after forced cleanup");
    
    //is(CFDictionaryGetCountOfValue(BobChanges, kCFNull), 1, "1 Keys Nulled Out");
    
//    CFDictionaryForEach(BobChanges, ^(const void *key, const void *value) {
//        if(isNull(value)) {
//            CFStringRef circle_name = NULL, retiree = NULL;
//            SOSKVSKeyType keytype = SOSKVSKeyGetKeyTypeAndParse(key, &circle_name, &retiree, NULL);
//            is(keytype, kRetirementKey, "Expect only a retirement key");
//            ok(CFEqualSafe(alice_id, retiree), "Alice (%@) is retiree (%@)", alice_id, retiree);
//            CFReleaseNull(circle_name);
//            CFReleaseNull(retiree);
//        }
//    });

    CFReleaseNull(alice_id);
    CFReleaseNull(carole_account);
    CFReleaseNull(bob_account);
    CFReleaseNull(alice_account);
    
    SOSTestCleanup();
}
static void tests(void)
{
    CFErrorRef error = NULL;
    CFDataRef cfpassword = CFDataCreate(NULL, kAccountPasswordString, kAccountPasswordStringLen);
    CFStringRef cfaccount = CFSTR("*****@*****.**");

    CFMutableDictionaryRef changes = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);

    SOSAccountRef alice_account = CreateAccountForLocalChanges(CFSTR("Alice"), CFSTR("TestSource"));
    SOSAccountRef bob_account = CreateAccountForLocalChanges(CFSTR("Bob"), CFSTR("TestSource"));
    SOSAccountRef carole_account = CreateAccountForLocalChanges(CFSTR("Carole"), CFSTR("TestSource"));
    SOSAccountRef david_account = CreateAccountForLocalChanges(CFSTR("David"), CFSTR("TestSource"));
    
    ok(SOSAccountAssertUserCredentialsAndUpdate(bob_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
    
    // Bob wins writing at this point, feed the changes back to alice.
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, david_account, NULL), 1, "updates");

    ok(SOSAccountAssertUserCredentialsAndUpdate(alice_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
    CFReleaseNull(error);
    
    ok(SOSAccountAssertUserCredentialsAndUpdate(carole_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
    CFReleaseNull(error);
    
    ok(SOSAccountAssertUserCredentialsAndUpdate(david_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
    CFReleaseNull(cfpassword);
    CFReleaseNull(error);
    
    ok(SOSAccountResetToOffering_wTxn(alice_account, &error), "Reset to offering (%@)", error);
    CFReleaseNull(error);
    
    // Lost Application Scenario
    is(ProcessChangesOnce(changes, alice_account, bob_account, carole_account, david_account, NULL), 1, "updates");

    ok(SOSAccountJoinCircles_wTxn(bob_account, &error), "Bob Applies (%@)", error);
    CFReleaseNull(error);
    
    ok(SOSAccountJoinCircles_wTxn(carole_account, &error), "Carole Applies too (%@)", error);
    CFReleaseNull(error);
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, david_account, NULL), 3, "updates");
    
    accounts_agree("alice and carole agree", alice_account, carole_account);
    accounts_agree("alice and bob agree", alice_account, bob_account);
    
    {
        CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error);
        
        ok(applicants && CFArrayGetCount(applicants) == 2, "See two applicants %@ (%@)", applicants, error);
        CFReleaseNull(error);
        CFReleaseSafe(applicants);
    }
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, david_account, NULL), 1, "updates");

    accounts_agree("alice and carole agree", alice_account, carole_account);

    CFReleaseNull(error);
    
    {
        CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error);
        ok(applicants && CFArrayGetCount(applicants) == 2, "See two applicants %@ (%@)", applicants, error);
        ok(SOSAccountRejectApplicants(alice_account, applicants, &error), "Everyone out the pool");
        CFReleaseNull(error);
        CFReleaseSafe(applicants);
    }

    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, david_account, NULL), 2, "updates");

    accounts_agree("alice and carole agree", alice_account, carole_account);

    {
        CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error);
        ok(applicants && CFArrayGetCount(applicants) == 0, "See no applicants %@ (%@)", applicants, error);
        CFReleaseNull(error);
        CFReleaseSafe(applicants);
    }
    
    ok(SOSAccountLeaveCircle(carole_account, &error), "Carole bails (%@)", error);
    CFReleaseNull(error);

    // Everyone but bob sees that carole bails.
    is(ProcessChangesUntilNoChange(changes, alice_account, carole_account, david_account, NULL), 1, "updates");


    // Bob reapplies, but it's to an old circle.
    ok(SOSAccountJoinCircles_wTxn(bob_account, &error), "Bob asks again");
    CFReleaseNull(error);

    // Bob returns and we mix our split worlds up.
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, david_account, NULL), 2, "updates");

    {
        CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error);
        ok(applicants && CFArrayGetCount(applicants) == 1, "See one applicants %@ (%@)", applicants, error);
        CFReleaseNull(error);
        ok(SOSAccountAcceptApplicants(alice_account, applicants, &error), "Accept bob into the fold");
        CFReleaseNull(error);
        CFReleaseSafe(applicants);
    }

    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, david_account, NULL), 3, "updates");

    is(countPeers(bob_account), 2, "Bob sees 2 valid peers after admission from re-apply");

    accounts_agree("alice and bob agree", alice_account, bob_account);
    accounts_agree_internal("alice and carole agree", alice_account, carole_account, false);


    // Rejected Application Scenario
    ok(SOSAccountJoinCircles_wTxn(david_account, &error), "Dave Applies (%@)", error);
    CFReleaseNull(error);

    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, david_account, NULL), 2, "updates");

    accounts_agree_internal("alice and david agree", alice_account, david_account, false);

    SOSAccountPurgePrivateCredential(alice_account);

    {
        CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error);

        ok(applicants && CFArrayGetCount(applicants) == 1, "See one applicant %@ (%@)", applicants, error);
        ok(SOSAccountRejectApplicants(alice_account, applicants, &error), "Alice rejects (%@)", error);
        CFReleaseNull(error);
        CFReleaseNull(applicants);
    }

    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, david_account, NULL), 2, "updates");

    accounts_agree_internal("alice and carole still agree after david is rejected", alice_account, carole_account, false);

    cfpassword = CFDataCreate(NULL, kAccountPasswordString, kAccountPasswordStringLen);

    ok(SOSAccountTryUserCredentials(alice_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
    CFReleaseNull(error);
    CFReleaseNull(cfpassword);

    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, david_account, NULL), 1, "updates");

    accounts_agree("bob&alice pair", bob_account, alice_account);

    ok(SOSAccountJoinCirclesAfterRestore_wTxn(carole_account, &error), "Carole cloud identiy joins (%@)", error);
    CFReleaseNull(error);

    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, david_account, NULL), 4, "updates");

    accounts_agree_internal("carole&alice pair", carole_account, alice_account, false);

    is(countPeers(carole_account), 3, "Carole sees 3 valid peers after sliding in");

    CFReleaseNull(bob_account);
    CFReleaseNull(alice_account);
    CFReleaseNull(carole_account);
    CFReleaseNull(david_account);
    
    SOSTestCleanup();
}
static void tests(void)
{
    CFErrorRef error = NULL;
    CFDataRef cfpassword = CFDataCreate(NULL, (uint8_t *) "FooFooFoo", 10);
    CFStringRef cfaccount = CFSTR("*****@*****.**");

    CFMutableDictionaryRef changes = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);

    SOSAccountRef alice_account = CreateAccountForLocalChanges(CFSTR("Alice"), CFSTR("TestSource"));
    SOSAccountRef bob_account = CreateAccountForLocalChanges(CFSTR("Bob"), CFSTR("TestSource"));
    SOSAccountRef carole_account = CreateAccountForLocalChanges(CFSTR("Carole"), CFSTR("TestSource"));
    SOSAccountRef david_account = CreateAccountForLocalChanges(CFSTR("David"), CFSTR("TestSource"));
    
    ok(SOSAccountAssertUserCredentials(bob_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
    
    // Bob wins writing at this point, feed the changes back to alice.
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, david_account, NULL), 1, "updates");

    ok(SOSAccountAssertUserCredentials(alice_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
    CFReleaseNull(error);
    
    ok(SOSAccountAssertUserCredentials(carole_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
    CFReleaseNull(error);
    
    ok(SOSAccountAssertUserCredentials(david_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
    CFReleaseNull(cfpassword);
    CFReleaseNull(error);
    
    ok(SOSAccountResetToOffering(alice_account, &error), "Reset to offering (%@)", error);
    CFReleaseNull(error);
    
    // Lost Application Scenario
    is(ProcessChangesOnce(changes, alice_account, bob_account, carole_account, david_account, NULL), 1, "updates");

    ok(SOSAccountJoinCircles(bob_account, &error), "Bob Applies (%@)", error);
    CFReleaseNull(error);
    
    ok(SOSAccountJoinCircles(carole_account, &error), "Carole Applies too (%@)", error);
    CFReleaseNull(error);
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, david_account, NULL), 3, "updates");
    
    accounts_agree("alice and carole agree", alice_account, carole_account);
    accounts_agree("alice and bob agree", alice_account, bob_account);
    
    {
        CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error);
        
        ok(applicants && CFArrayGetCount(applicants) == 2, "See two applicants %@ (%@)", applicants, error);
        CFReleaseNull(error);
        CFReleaseSafe(applicants);
    }
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, david_account, NULL), 1, "updates");

    accounts_agree("alice and carole agree", alice_account, carole_account);

    CFReleaseNull(error);
    
    {
        CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error);
        ok(applicants && CFArrayGetCount(applicants) == 2, "See two applicants %@ (%@)", applicants, error);
        ok(SOSAccountRejectApplicants(alice_account, applicants, &error), "Everyone out the pool");
        CFReleaseNull(error);
        CFReleaseSafe(applicants);
    }

    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, david_account, NULL), 2, "updates");

    accounts_agree("alice and carole agree", alice_account, carole_account);

    {
        CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error);
        ok(applicants && CFArrayGetCount(applicants) == 0, "See no applicants %@ (%@)", applicants, error);
        CFReleaseNull(error);
        CFReleaseSafe(applicants);
    }
    
    ok(SOSAccountJoinCircles(bob_account, &error), "Bob asks again");
    CFReleaseNull(error);

    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, david_account, NULL), 2, "updates");

    {
        CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error);
        ok(applicants && CFArrayGetCount(applicants) == 1, "See one applicants %@ (%@)", applicants, error);
        CFReleaseNull(error);
        ok(SOSAccountAcceptApplicants(alice_account, applicants, &error), "Accept bob into the fold");
        CFReleaseNull(error);
        CFReleaseSafe(applicants);
    }

    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, david_account, NULL), 3, "updates");

#if 0
    
    {
        CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error);
        
        ok(applicants && CFArrayGetCount(applicants) == 1, "Bob automatically re-applied %@ (%@)", applicants, error);
        ok(SOSAccountAcceptApplicants(alice_account, applicants, &error), "Alice accepts (%@)", error);
        CFReleaseNull(error);
        CFReleaseNull(applicants);
    }
    
    is(countPeers(alice_account, 0), 3, "Bob is accepted after auto-reapply");
    
    FillAllChanges(changes);
    FeedChangesToMulti(AliceChanges, bob_account, carole_account, david_account, NULL);
    FeedChangesToMulti(BobChanges, alice_account, carole_account, david_account, NULL);
    FeedChangesToMulti(CarolChanges, bob_account, alice_account, david_account, NULL);
    FeedChangesToMulti(DavidChanges, bob_account, alice_account, carole_account, NULL);
    
    FillAllChanges(changes);
    FeedChangesToMulti(AliceChanges, bob_account, carole_account, david_account, NULL);
    FeedChangesToMulti(BobChanges, alice_account, carole_account, david_account, NULL);
    FeedChangesToMulti(CarolChanges, bob_account, alice_account, david_account, NULL);
    FeedChangesToMulti(DavidChanges, bob_account, alice_account, carole_account, NULL);
    
    FillAllChanges(changes);
    FeedChangesToMulti(AliceChanges, bob_account, carole_account, david_account, NULL);
    FeedChangesToMulti(BobChanges, alice_account, carole_account, david_account, NULL);
    FeedChangesToMulti(CarolChanges, bob_account, alice_account, david_account, NULL);
    FeedChangesToMulti(DavidChanges, bob_account, alice_account, carole_account, NULL);
    accounts_agree("alice and carole agree after bob gets in", alice_account, carole_account);
    
    // Rejected Application Scenario
    ok(SOSAccountJoinCircles(david_account, &error), "Dave Applies (%@)", error);
    CFReleaseNull(error);
    
    FillAllChanges(changes);
    FeedChangesToMulti(AliceChanges, bob_account, carole_account, david_account, NULL);
    FeedChangesToMulti(BobChanges, alice_account, carole_account, david_account, NULL);
    FeedChangesToMulti(CarolChanges, bob_account, alice_account, david_account, NULL);
    FeedChangesToMulti(DavidChanges, bob_account, alice_account, carole_account, NULL);
    
    SOSAccountPurgePrivateCredential(alice_account);
    
    {
        CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error);
        
        ok(applicants && CFArrayGetCount(applicants) == 1, "See one applicant %@ (%@)", applicants, error);
        ok(SOSAccountRejectApplicants(alice_account, applicants, &error), "Alice rejects (%@)", error);
        CFReleaseNull(error);
        CFReleaseNull(applicants);
    }
    
    FillAllChanges(changes);
    FeedChangesToMulti(AliceChanges, bob_account, carole_account, david_account, NULL);
    FeedChangesToMulti(BobChanges, alice_account, carole_account, david_account, NULL);
    FeedChangesToMulti(CarolChanges, bob_account, alice_account, david_account, NULL);
    FeedChangesToMulti(DavidChanges, bob_account, alice_account, carole_account, NULL);
    
    FillAllChanges(changes);
    FeedChangesToMulti(AliceChanges, bob_account, carole_account, david_account, NULL);
    FeedChangesToMulti(BobChanges, alice_account, carole_account, david_account, NULL);
    FeedChangesToMulti(CarolChanges, bob_account, alice_account, david_account, NULL);
    FeedChangesToMulti(DavidChanges, bob_account, alice_account, carole_account, NULL);
    
    FillAllChanges(changes);
    FeedChangesToMulti(AliceChanges, bob_account, carole_account, david_account, NULL);
    FeedChangesToMulti(BobChanges, alice_account, carole_account, david_account, NULL);
    FeedChangesToMulti(CarolChanges, bob_account, alice_account, david_account, NULL);
    FeedChangesToMulti(DavidChanges, bob_account, alice_account, carole_account, NULL);
    
    accounts_agree("alice and carole still agree after david is rejected", alice_account, carole_account);
    ok(SOSAccountTryUserCredentials(alice_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
    CFReleaseNull(error);
    
    FillAllChanges(changes);
    FeedChangesToMulti(AliceChanges, bob_account, carole_account, david_account, NULL);
    FeedChangesToMulti(BobChanges, alice_account, carole_account, david_account, NULL);
    FeedChangesToMulti(CarolChanges, bob_account, alice_account, david_account, NULL);
    FeedChangesToMulti(DavidChanges, bob_account, alice_account, carole_account, NULL);
    
    FillAllChanges(changes);
    
    ok(CFDictionaryGetCount(CarolChanges) == 0, "We converged. (%@)", CarolChanges);
    ok(CFDictionaryGetCount(BobChanges) == 0, "We converged. (%@)", BobChanges);
    ok(CFDictionaryGetCount(AliceChanges) == 0, "We converged. (%@)", AliceChanges);
    ok(CFDictionaryGetCount(DavidChanges) == 0, "We converged. (%@)", DavidChanges);
    
    accounts_agree("bob&alice pair", bob_account, alice_account);
    
    ok(SOSAccountJoinCirclesAfterRestore(carole_account, &error), "Carole cloud identiy joins (%@)", error);
    CFReleaseNull(error);
    
    is(countPeers(carole_account, false), 3, "Carole sees 3 valid peers after sliding in");
    
    FillAllChanges(changes);
    FeedChangesToMulti(AliceChanges, bob_account, carole_account, david_account, NULL);
    FeedChangesToMulti(BobChanges, alice_account, carole_account, david_account, NULL);
    FeedChangesToMulti(CarolChanges, bob_account, alice_account, david_account, NULL);
    FeedChangesToMulti(DavidChanges, bob_account, alice_account, carole_account, NULL);
    
    FillAllChanges(changes);
    FeedChangesToMulti(AliceChanges, bob_account, carole_account, david_account, NULL);
    FeedChangesToMulti(BobChanges, alice_account, carole_account, david_account, NULL);
    FeedChangesToMulti(CarolChanges, bob_account, alice_account, david_account, NULL);
    FeedChangesToMulti(DavidChanges, bob_account, alice_account, carole_account, NULL);
    
    FillAllChanges(changes);
    FeedChangesToMulti(AliceChanges, bob_account, carole_account, david_account, NULL);
    FeedChangesToMulti(BobChanges, alice_account, carole_account, david_account, NULL);
    FeedChangesToMulti(CarolChanges, bob_account, alice_account, david_account, NULL);
    FeedChangesToMulti(DavidChanges, bob_account, alice_account, carole_account, NULL); // Bob and carole see the final result.
    
    accounts_agree_internal("Carole's in", bob_account, alice_account, false);
    accounts_agree_internal("Carole's in - 2", bob_account, carole_account, false);
#endif
    CFReleaseNull(bob_account);
    CFReleaseNull(alice_account);
    CFReleaseNull(carole_account);
    
    SOSUnregisterAllTransportMessages();
    SOSUnregisterAllTransportCircles();
    SOSUnregisterAllTransportKeyParameters();
    CFArrayRemoveAllValues(key_transports);
    CFArrayRemoveAllValues(circle_transports);
    CFArrayRemoveAllValues(message_transports);
    
}
static void tests(void)
{
    CFErrorRef error = NULL;
    CFDataRef cfpassword = CFDataCreate(NULL, (uint8_t *) "FooFooFoo", 10);
    CFDataRef cfwrong_password = CFDataCreate(NULL, (uint8_t *) "NotFooFooFoo", 10);
    CFStringRef cfaccount = CFSTR("*****@*****.**");
    
    CFMutableDictionaryRef changes = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
    SOSAccountRef alice_account = CreateAccountForLocalChanges(CFSTR("Alice"), CFSTR("TestSource"));
    SOSAccountRef bob_account = CreateAccountForLocalChanges(CFSTR("Bob"), CFSTR("TestSource"));
    SOSAccountRef carol_account = CreateAccountForLocalChanges(CFSTR("Carol"), CFSTR("TestSource"));
    
    ok(SOSAccountAssertUserCredentialsAndUpdate(bob_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);

    // Bob wins writing at this point, feed the changes back to alice.
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 1, "updates");
    
    ok(SOSAccountAssertUserCredentialsAndUpdate(alice_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
    CFReleaseNull(error);
    ok(SOSAccountTryUserCredentials(alice_account, cfaccount, cfpassword, &error), "Credential trying (%@)", error);
    CFReleaseNull(error);
    ok(!SOSAccountTryUserCredentials(alice_account, cfaccount, cfwrong_password, &error), "Credential failing (%@)", error);
    CFReleaseNull(cfwrong_password);
    is(error ? CFErrorGetCode(error) : 0, kSOSErrorWrongPassword, "Expected SOSErrorWrongPassword");
    CFReleaseNull(error);
    
    ok(SOSAccountResetToOffering_wTxn(alice_account, &error), "Reset to offering (%@)", error);
    CFReleaseNull(error);
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 2, "updates");
    
    ok(SOSAccountJoinCircles_wTxn(bob_account, &error), "Bob Applies (%@)", error);
    CFReleaseNull(error);
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 2, "updates");
    
    {
        CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error);
        
        ok(applicants && CFArrayGetCount(applicants) == 1, "See one applicant %@ (%@)", applicants, error);
        ok(SOSAccountAcceptApplicants(alice_account, applicants, &error), "Alice accepts (%@)", error);
        CFReleaseNull(error);
        CFReleaseNull(applicants);
    }
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 3, "updates");
    
    accounts_agree("bob&alice pair", bob_account, alice_account);
    
    CFArrayRef peers = SOSAccountCopyPeers(alice_account, &error);
    ok(peers && CFArrayGetCount(peers) == 2, "See two peers %@ (%@)", peers, error);
    CFReleaseNull(peers);
    
    //bob now goes def while Alice does some stuff.
    
    ok(SOSAccountLeaveCircle(alice_account, &error), "ALICE LEAVES THE CIRCLE (%@)", error);
    ok(SOSAccountResetToOffering_wTxn(alice_account, &error), "Alice resets to offering again (%@)", error);
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 2, "updates");

    accounts_agree("bob&alice pair", bob_account, alice_account);

    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carol_account, NULL), 1, "updates");

    
    ok(SOSAccountAssertUserCredentialsAndUpdate(carol_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
    SOSAccountSetUserPublicTrustedForTesting(carol_account);
    ok(SOSAccountResetToOffering_wTxn(carol_account, &error), "Carol is going to push a reset to offering (%@)", error);

    int64_t valuePtr = 0;
    CFNumberRef gencount = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &valuePtr);
    SOSCircleSetGeneration(carol_account->trusted_circle, gencount);
    
    SecKeyRef user_privkey = SOSUserKeygen(cfpassword, carol_account->user_key_parameters, &error);
    CFNumberRef genCountTest = SOSCircleGetGeneration(carol_account->trusted_circle);
    CFIndex testPtr;
    CFNumberGetValue(genCountTest, kCFNumberCFIndexType, &testPtr);
    ok(testPtr== 0);

    SOSCircleSignOldStyleResetToOfferingCircle(carol_account->trusted_circle, carol_account->my_identity, user_privkey, &error);
    SOSTransportCircleTestRemovePendingChange(carol_account->circle_transport, SOSCircleGetName(carol_account->trusted_circle), NULL);
    CFDataRef circle_data = SOSCircleCopyEncodedData(carol_account->trusted_circle, kCFAllocatorDefault, &error);
    if (circle_data) {
         SOSTransportCirclePostCircle(carol_account->circle_transport, SOSCircleGetName(carol_account->trusted_circle), circle_data, &error);
    }

    genCountTest = SOSCircleGetGeneration(carol_account->trusted_circle);
    CFNumberGetValue(genCountTest, kCFNumberCFIndexType, &testPtr);
    ok(testPtr== 0);

    ok(SOSAccountAssertUserCredentialsAndUpdate(bob_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
    ok(SOSAccountAssertUserCredentialsAndUpdate(alice_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);

    SOSAccountSetUserPublicTrustedForTesting(alice_account);
    SOSAccountSetUserPublicTrustedForTesting(bob_account);

    is(ProcessChangesUntilNoChange(changes, carol_account, alice_account, bob_account, NULL), 2, "updates");

    ok(kSOSCCNotInCircle == SOSAccountGetCircleStatus(alice_account, &error), "alice is not in the account (%@)", error);
    ok(kSOSCCNotInCircle == SOSAccountGetCircleStatus(bob_account, &error), "bob is not in the account (%@)", error);
    ok(kSOSCCInCircle == SOSAccountGetCircleStatus(carol_account, &error), "carol is in the account (%@)", error);
    
    CFReleaseNull(gencount);
    CFReleaseNull(bob_account);
    CFReleaseNull(alice_account);
    CFReleaseNull(cfpassword);
    SOSTestCleanup();
}
static void tests(void)
{
    CFErrorRef error = NULL;
    CFDataRef cfpassword = CFDataCreate(NULL, (uint8_t *) "FooFooFoo", 10);
    CFStringRef cfaccount = CFSTR("*****@*****.**");
    
    CFMutableDictionaryRef changes = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);

    SOSAccountRef alice_account = CreateAccountForLocalChanges( CFSTR("Alice"), CFSTR("TestSource"));
    SOSAccountRef bob_account = CreateAccountForLocalChanges(CFSTR("Bob"), CFSTR("TestSource"));
    SOSAccountRef carole_account = CreateAccountForLocalChanges(CFSTR("Carole"), CFSTR("TestSource"));
    
    ok(SOSAccountAssertUserCredentialsAndUpdate(bob_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
    
    // Bob wins writing at this point, feed the changes back to alice.
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 1, "updates");

    ok(SOSAccountAssertUserCredentialsAndUpdate(alice_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
    CFReleaseNull(error);
    
    ok(SOSAccountAssertUserCredentialsAndUpdate(carole_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
    CFReleaseNull(error);
    ok(SOSAccountResetToOffering_wTxn(alice_account, &error), "Reset to offering (%@)", error);
    CFReleaseNull(error);
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 2, "updates");

    ok(SOSAccountJoinCircles_wTxn(bob_account, &error), "Bob Applies (%@)", error);
    CFReleaseNull(error);
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 2, "updates");

    {
        CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error);
        
        ok(applicants && CFArrayGetCount(applicants) == 1, "See one applicant %@ (%@)", applicants, error);
        ok(SOSAccountAcceptApplicants(alice_account, applicants, &error), "Alice accepts (%@)", error);
        CFReleaseNull(error);
        CFReleaseNull(applicants);
    }
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 3, "updates");

    accounts_agree("bob&alice pair", bob_account, alice_account);
    
    /*----- normal join after restore -----*/
    
    ok(SOSAccountJoinCirclesAfterRestore_wTxn(carole_account, &error), "Carole cloud identity joins (%@)", error);
    CFReleaseNull(error);
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 4, "updates");

    is(countApplicants(alice_account), 0, "See no applicants");
    
    is(countPeers(carole_account), 3, "Carole sees 3 valid peers after sliding in");
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 1, "updates");

    accounts_agree_internal("Carole's in", bob_account, alice_account, false);
    accounts_agree_internal("Carole's in - 2", bob_account, carole_account, false);
    
    ok(SOSAccountLeaveCircle(carole_account, &error), "Carol Leaves again");
    CFReleaseNull(error);
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 2, "updates");

    /*----- join - join after restore -----*/
    
    ok(SOSAccountJoinCircles_wTxn(carole_account, &error), "Carole normally joins (%@)", error);
    CFReleaseNull(error);
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 2, "updates");

    is(countApplicants(alice_account), 1, "See one applicant");
    
    ok(SOSAccountJoinCirclesAfterRestore_wTxn(carole_account, &error), "Carole cloud identity joins (%@)", error);
    CFReleaseNull(error);
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 4, "updates");


    is(countApplicants(alice_account), 0, "See no applicants");
    
    is(countPeers(carole_account), 3, "Carole sees 3 valid peers after sliding in");

    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 1, "updates");

    accounts_agree_internal("Carole's in", bob_account, alice_account, false);
    accounts_agree_internal("Carole's in - 2", bob_account, carole_account, false);
    
    /* Break iCloud identity FPI in all peers */
    
    ok(purgeICloudIdentity(alice_account), "remove iCloud private key");
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 1, "updates");

    
    ok(SOSAccountAssertUserCredentialsAndUpdate(alice_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
    CFReleaseNull(error);
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 4, "updates");
    
    ok(SOSAccountLeaveCircle(carole_account, &error), "Carol Leaves again");
    CFReleaseNull(error);
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 2, "updates");
    
    /*----- join - join after restore -----*/
    
    ok(SOSAccountJoinCircles_wTxn(carole_account, &error), "Carole normally joins (%@)", error);
    CFReleaseNull(error);
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 2, "updates");
    
    is(countApplicants(alice_account), 1, "See one applicant");
    
    ok(SOSAccountJoinCirclesAfterRestore_wTxn(carole_account, &error), "Carole cloud identity joins (%@)", error);
    CFReleaseNull(error);
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 4, "updates");
    
    
    is(countApplicants(alice_account), 0, "See no applicants");
    
    is(countPeers(carole_account), 3, "Carole sees 3 valid peers after sliding in");
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carole_account, NULL), 1, "updates");
    
    accounts_agree_internal("Carole's in", bob_account, alice_account, false);
    accounts_agree_internal("Carole's in - 2", bob_account, carole_account, false);

    CFReleaseNull(bob_account);
    CFReleaseNull(alice_account);
    CFReleaseNull(carole_account);
    CFReleaseNull(cfpassword);

    SOSTestCleanup();
}
static void tests(void)
{
    CFErrorRef error = NULL;
    CFDataRef cfpassword = CFDataCreate(NULL, (uint8_t *) "FooFooFoo", 10);
    CFDataRef cfwrong_password = CFDataCreate(NULL, (uint8_t *) "NotFooFooFoo", 10);
    CFStringRef cfaccount = CFSTR("*****@*****.**");
    
    CFMutableDictionaryRef changes = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
    SOSAccountRef alice_account = CreateAccountForLocalChanges(CFSTR("Alice"), CFSTR("TestSource"));
    SOSAccountRef bob_account = CreateAccountForLocalChanges(CFSTR("Bob"), CFSTR("TestSource"));
    SOSAccountRef carol_account = CreateAccountForLocalChanges(CFSTR("Carol"), CFSTR("TestSource"));
    
    ok(SOSAccountAssertUserCredentialsAndUpdate(bob_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
    
    // Bob wins writing at this point, feed the changes back to alice.
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carol_account, NULL), 1, "updates");

    ok(SOSAccountAssertUserCredentialsAndUpdate(alice_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
    CFReleaseNull(error);
    ok(SOSAccountTryUserCredentials(alice_account, cfaccount, cfpassword, &error), "Credential trying (%@)", error);
    CFReleaseNull(error);
    ok(!SOSAccountTryUserCredentials(alice_account, cfaccount, cfwrong_password, &error), "Credential failing (%@)", error);
    CFReleaseNull(cfwrong_password);
    is(error ? CFErrorGetCode(error) : 0, kSOSErrorWrongPassword, "Expected SOSErrorWrongPassword");
    CFReleaseNull(error);
    
    ok(SOSAccountResetToOffering_wTxn(alice_account, &error), "Reset to offering (%@)", error);
    CFReleaseNull(error);
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carol_account, NULL), 2, "updates");

    ok(SOSAccountHasCompletedInitialSync(alice_account), "Alice thinks she's completed initial sync");

    ok(SOSAccountJoinCircles_wTxn(bob_account, &error), "Bob Applies (%@)", error);
    CFReleaseNull(error);
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carol_account, NULL), 2, "updates");

    {
        CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error);
        
        ok(applicants && CFArrayGetCount(applicants) == 1, "See one applicant %@ (%@)", applicants, error);
        ok(SOSAccountAcceptApplicants(alice_account, applicants, &error), "Alice accepts (%@)", error);
        CFReleaseNull(error);
        CFReleaseNull(applicants);
    }
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carol_account, NULL), 3, "updates");

    accounts_agree("bob&alice pair", bob_account, alice_account);
    
    CFArrayRef peers = SOSAccountCopyPeers(alice_account, &error);
    ok(peers && CFArrayGetCount(peers) == 2, "See two peers %@ (%@)", peers, error);
    CFReleaseNull(peers);

    ok(!SOSAccountHasCompletedInitialSync(bob_account), "Bob thinks he hasn't completed initial sync");

    CFDictionaryRef alice_new_gestalt = SOSCreatePeerGestaltFromName(CFSTR("Alice, but different"));

    ok(SOSAccountUpdateGestalt(alice_account, alice_new_gestalt), "Update gestalt %@ (%@)", alice_account, error);
    SOSAccountUpdateTestTransports(alice_account, alice_new_gestalt);
    CFReleaseNull(alice_new_gestalt);
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carol_account, NULL), 2, "updates");

    accounts_agree("Alice's name changed", bob_account, alice_account);
    
    ok(SOSAccountLeaveCircle(alice_account, &error), "Alice Leaves (%@)", error);
    CFReleaseNull(error);
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carol_account, NULL), 2, "updates");

    accounts_agree("Alice bails", bob_account, alice_account);
    
    peers = SOSAccountCopyPeers(alice_account, &error);
    ok(peers && CFArrayGetCount(peers) == 1, "See one peer %@ (%@)", peers, error);
    CFReleaseNull(peers);
    
    ok(SOSAccountJoinCircles_wTxn(alice_account, &error), "Alice re-applies (%@)", error);
    CFReleaseNull(error);
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carol_account, NULL), 2, "updates");

    
    {
        CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error);
        
        ok(applicants && CFArrayGetCount(applicants) == 1, "See one applicant %@ (%@)", applicants, error);
        ok(SOSAccountAcceptApplicants(bob_account, applicants, &error), "Bob accepts (%@)", error);
        CFReleaseNull(error);
        CFReleaseNull(applicants);
    }
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carol_account, NULL), 3, "updates");

    accounts_agree("Alice accepts' Bob", bob_account, alice_account);
    
    ok(SOSAccountLeaveCircle(alice_account, &error), "Alice Leaves (%@)", error);
    CFReleaseNull(error);
    ok(SOSAccountJoinCircles_wTxn(alice_account, &error), "Alice re-applies (%@)", error);
    CFReleaseNull(error);
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carol_account, NULL), 2, "updates");

    {
        CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error);
        
        ok(applicants && CFArrayGetCount(applicants) == 1, "See one applicant %@ (%@)", applicants, error);
        ok(SOSAccountAcceptApplicants(bob_account, applicants, &error), "Bob accepts (%@)", error);
        CFReleaseNull(error);
        CFReleaseNull(applicants);
    }
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carol_account, NULL), 3, "updates");

    accounts_agree("Bob accepts Alice", bob_account, alice_account);
    
    // As of PR-13917727/PR-13906870 this no longer works (by "design"), in favor of making another more common
    // failure (apply/OSX-psudo-reject/re-apply) work.  Write races might be "fixed better" with affirmitave rejection.
#if 0
    
    //
    // Write race emulation.
    //
    //
    
    ok(SOSAccountLeaveCircle(alice_account, &error), "Alice Leaves (%@)", error);
    CFReleaseNull(error);
    FeedChangesTo(changes, bob_account); // Bob sees Alice leaving and rejoining
    FeedChangesTo(changes, alice_account); // Alice sees bob concurring
    
    accounts_agree("Alice leaves & returns", bob_account, alice_account);
    
    ok(SOSAccountJoinCircles(alice_account, &error), "Alice re-applies (%@)", error);
    CFReleaseNull(error);
    
    FeedChangesTo(changes, bob_account); // Bob sees Alice Applying
    
    {
        CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error);
        
        ok(applicants && CFArrayGetCount(applicants) == 1, "See one applicant %@ (%@)", applicants, error);
        ok(SOSAccountAcceptApplicants(bob_account, applicants, &error), "Bob accepts (%@)", error);
        CFReleaseNull(error);
        CFReleaseNull(applicants);
    }
    
    CFMutableDictionaryRef bobAcceptanceChanges = ExtractPendingChanges(changes);
    
    // Alice re-applies without seeing that she was accepted.
    ok(SOSAccountLeaveCircles(alice_account, &error), "Alice Leaves again  (%@)", error);
    CFReleaseNull(error);
    ok(SOSAccountJoinCircles(alice_account, &error), "Alice re-applies (%@)", error);
    CFReleaseNull(error);
    
    CFReleaseSafe(ExtractPendingChanges(changes)); // Alice loses the race to write her changes - bob never sees em.
    
    FeedChangesTo(&bobAcceptanceChanges, alice_account); // Alice sees bob inviting her in the circle.
    
    FeedChangesTo(changes, bob_account); // Bob sees Alice Concurring
    
    // As of PR-13917727/PR-13906870
    accounts_agree("Alice leave, applies back, loses a race and eventually gets in", bob_account, alice_account);
#endif
    
    // Both in circle.
    
    // Emulation of <rdar://problem/13919554> Innsbruck11A368 +Roots: Device A was removed when Device B joined.
    
    // We want Alice to leave circle while an Applicant on a full concordance signed circle with old-Alice as an Alum and Bob a peer.
    // ZZZ
    ok(SOSAccountLeaveCircle(alice_account, &error), "Alice leaves once more  (%@)", error);
    CFReleaseNull(error);
    
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carol_account, NULL), 2, "updates");
    accounts_agree("Alice and Bob see Alice out of circle", bob_account, alice_account);
    
    ok(SOSAccountJoinCircles_wTxn(alice_account, &error), "Alice re-applies (%@)", error);
    CFReleaseNull(error);
    
    ok(SOSAccountLeaveCircle(alice_account, &error), "Alice leaves while applying  (%@)", error);
    FeedChangesTo(changes, bob_account); // Bob sees Alice become an Alum.
    
    CFReleaseNull(error);
    
    is(SOSAccountGetCircleStatus(alice_account, &error), kSOSCCNotInCircle, "Alice isn't applying any more");
    accounts_agree("Alice leaves & some fancy concordance stuff happens", bob_account, alice_account);
    
    ok(SOSAccountJoinCircles_wTxn(alice_account, &error), "Alice re-applies (%@)", error);
    CFReleaseNull(error);
    
    FeedChangesTo(changes, bob_account); // Bob sees Alice reapply.
    
    {
        CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error);
        
        ok(applicants && CFArrayGetCount(applicants) == 1, "See one applicant %@ (%@)", applicants, error);
        ok(SOSAccountAcceptApplicants(bob_account, applicants, &error), "Bob accepts (%@)", error);
        CFReleaseNull(error);
        CFReleaseNull(applicants);
    }
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carol_account, NULL), 2, "updates");

    accounts_agree("Alice comes back", bob_account, alice_account);
    
    // Emulation of <rdar://problem/13889901>
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carol_account, NULL), 1, "updates");

    ok(SOSAccountAssertUserCredentialsAndUpdate(carol_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error);
    CFReleaseNull(cfpassword);
    ok(SOSAccountJoinCircles_wTxn(carol_account, &error), "Carol Applies (%@)", error);
    CFReleaseNull(error);

    CFMutableDictionaryRef dropped_changes = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
    FillChanges(dropped_changes, carol_account);
    CFReleaseNull(dropped_changes);

    ok(SOSAccountResetToOffering_wTxn(carol_account, &error), "Reset to offering (%@)", error);
    CFReleaseNull(error);
    
    is(ProcessChangesUntilNoChange(changes, bob_account, carol_account,  NULL), 2, "updates");
    accounts_agree("13889901", carol_account, bob_account);
    is(SOSAccountGetLastDepartureReason(bob_account, &error), kSOSMembershipRevoked, "Bob affirms he hasn't left.");
    
    ok(SOSAccountJoinCircles_wTxn(bob_account, &error), "Bob ReApplies (%@)", error);
    is(ProcessChangesUntilNoChange(changes, bob_account, carol_account, NULL), 2, "updates");
    {
        CFArrayRef applicants = SOSAccountCopyApplicants(carol_account, &error);
        
        ok(applicants && CFArrayGetCount(applicants) == 1, "See one applicant %@ (%@)", applicants, error);
        ok(SOSAccountAcceptApplicants(carol_account, applicants, &error), "Carol accepts (%@)", error);
        CFReleaseNull(error);
        CFReleaseNull(applicants);
    }
    is(ProcessChangesUntilNoChange(changes, bob_account, carol_account, NULL), 3, "updates");
    accounts_agree("rdar://problem/13889901-II", bob_account, carol_account);

    // Alice has been out of the loop, bring her back up to speed before changing things (to avoid gen count race)
    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carol_account, NULL), 1, "Reset propogation");

    // Test multiple removal, including our own departure via that API
    ok(SOSAccountResetToOffering_wTxn(alice_account, NULL), "Reset to offering");

    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carol_account, NULL), 2, "Reset propogation");

    ok(SOSAccountJoinCircles_wTxn(bob_account, NULL), "bob joins again");

    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carol_account, NULL), 2, "Bob request");

    {
        CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error);

        ok(applicants && CFArrayGetCount(applicants) == 1, "See one applicant %@ (%@)", applicants, error);
        ok(SOSAccountAcceptApplicants(alice_account, applicants, &error), "Alice accepts (%@)", error);
        CFReleaseNull(error);
        CFReleaseNull(applicants);
    }

    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carol_account, NULL), 3, "carol request");

    ok(SOSAccountJoinCircles_wTxn(carol_account, NULL), "carol joins again");

    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carol_account, NULL), 2, "carol request");

    CFArrayRef peers_to_remove_array = CFArrayCreateForCFTypes(kCFAllocatorDefault,
                                                         SOSAccountGetMyPeerInfo(bob_account),
                                                         SOSAccountGetMyPeerInfo(carol_account),
                                                         NULL);

    ok(SOSAccountRemovePeersFromCircle(bob_account, peers_to_remove_array, NULL));

    is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, carol_account, NULL), 4, "Remove peers");

    ok(SOSAccountIsInCircle(alice_account, NULL), "Alice still here");
    ok(!SOSAccountIsInCircle(bob_account, NULL), "Bob not in circle");
    // Carol's not in circle, but reapplied, as she's persistent until positive rejection.
    ok(!SOSAccountIsInCircle(carol_account, NULL), "carol not in circle");

    CFReleaseNull(peers_to_remove_array);

    CFReleaseNull(alice_new_gestalt);
    
    CFReleaseNull(bob_account);
    CFReleaseNull(alice_account);
    CFReleaseNull(carol_account);
    SOSTestCleanup();
}