public void testDeleteFromClient() {
    SimpleEntity entity = new SimpleEntity();
    entity.setString("the string value");
    entity.setDate(new Timestamp(1234567L));
    entity.setInteger(9999);

    ErraiEntityManager esem = csm.getExpectedStateEm();
    ErraiEntityManager dsem = csm.getDesiredStateEm();

    // first persist the expected state
    esem.persist(entity);
    esem.flush();
    esem.clear();

    List<SyncRequestOperation<SimpleEntity>> expectedClientRequests =
        new ArrayList<SyncRequestOperation<SimpleEntity>>();
    expectedClientRequests.add(SyncRequestOperation.deleted(entity));

    // assuming no conflict, the server deletes the entity and generates the appropriate response
    List<SyncResponse<SimpleEntity>> fakeServerResponses =
        new ArrayList<SyncResponse<SimpleEntity>>();
    fakeServerResponses.add(new DeleteResponse<SimpleEntity>(entity));
    performColdSync(expectedClientRequests, fakeServerResponses);

    assertNull(esem.find(SimpleEntity.class, entity.getId()));
    assertNull(dsem.find(SimpleEntity.class, entity.getId()));
  }
  public void testDeleteFromServer() {
    SimpleEntity newEntity = new SimpleEntity();
    newEntity.setString("the string value");
    newEntity.setDate(new Timestamp(1234567L));
    newEntity.setInteger(9999);

    ErraiEntityManager esem = csm.getExpectedStateEm();
    ErraiEntityManager dsem = csm.getDesiredStateEm();

    // persist this as both the "expected state" from the server and the "desired state" on the
    // client
    SimpleEntity originalEntityState = esem.merge(newEntity);
    esem.flush();
    esem.clear();

    dsem.persist(originalEntityState);
    dsem.flush();
    dsem.clear();

    List<SyncRequestOperation<SimpleEntity>> expectedClientRequests =
        new ArrayList<SyncRequestOperation<SimpleEntity>>();
    expectedClientRequests.add(SyncRequestOperation.unchanged(originalEntityState));

    // now cook up a server response that says it got deleted
    List<SyncResponse<SimpleEntity>> fakeServerResponses =
        new ArrayList<SyncResponse<SimpleEntity>>();
    fakeServerResponses.add(new DeleteResponse<SimpleEntity>(newEntity));
    performColdSync(expectedClientRequests, fakeServerResponses);

    assertNull(esem.find(SimpleEntity.class, newEntity.getId()));
    assertNull(dsem.find(SimpleEntity.class, newEntity.getId()));

    // finally, ensure the deleted entity is not stuck in the REMOVED state
    // (should be NEW or DETACHED; we can verify by trying to merge it)
    try {
      esem.merge(newEntity);
    } catch (IllegalArgumentException e) {
      fail("Merging removed entity failed: " + e);
    }

    try {
      dsem.merge(newEntity);
    } catch (IllegalArgumentException e) {
      fail("Merging removed entity failed: " + e);
    }
  }
  public void testConcurrentSyncRequestsRejected() {
    SimpleEntity entity = new SimpleEntity();
    entity.setString("the string value");
    entity.setDate(new Timestamp(1234567L));
    entity.setInteger(9999);

    ErraiEntityManager esem = csm.getExpectedStateEm();
    ErraiEntityManager dsem = csm.getDesiredStateEm();

    // first persist the desired state
    SimpleEntity clientEntity = dsem.merge(entity);
    dsem.flush();
    dsem.clear();

    Long originalId = clientEntity.getId();
    List<SyncRequestOperation<SimpleEntity>> expectedClientRequests =
        new ArrayList<SyncRequestOperation<SimpleEntity>>();
    expectedClientRequests.add(SyncRequestOperation.created(clientEntity));

    // the server creates the entity with a different ID and notifies us of the change
    List<SyncResponse<SimpleEntity>> fakeServerResponses =
        new ArrayList<SyncResponse<SimpleEntity>>();
    SimpleEntity.setId(entity, 1010L);
    fakeServerResponses.add(new IdChangeResponse<SimpleEntity>(originalId, entity));

    Runnable doDuringSync =
        new Runnable() {

          boolean alreadyRunning = false;

          @Override
          public void run() {
            if (alreadyRunning) {
              fail("Detected recursive call to coldSync()");
            }
            alreadyRunning = true;

            // at this point, ClientSyncManager is in the middle of a coldSync call. for safety, it
            // is
            // required to fail.
            try {
              csm.coldSync(
                  "allSimpleEntities",
                  SimpleEntity.class,
                  Collections.<String, Object>emptyMap(),
                  new RemoteCallback<List<SyncResponse<SimpleEntity>>>() {
                    @Override
                    public void callback(List<SyncResponse<SimpleEntity>> response) {
                      fail("this recursive call to coldSync must not succeed");
                    }
                  },
                  new ErrorCallback<List<SyncResponse<SimpleEntity>>>() {
                    @Override
                    public boolean error(
                        List<SyncResponse<SimpleEntity>> message, Throwable throwable) {
                      fail("this recursive call to coldSync should have failed synchronously");
                      throw new AssertionError();
                    }
                  });
              fail("recursive call to coldSync() failed to throw an exception");
            } catch (IllegalStateException ex) {
              System.out.println(
                  "Got expected IllegalStateException. Returning normally so client state assertions can run.");
              // expected
            }
          }
        };

    performColdSync(expectedClientRequests, fakeServerResponses, doDuringSync);

    // now ensure the results of the original sync request were not harmed
    assertFalse(csm.isSyncInProgress());
    assertEquals(esem.find(SimpleEntity.class, entity.getId()).toString(), entity.toString());
    assertEquals(dsem.find(SimpleEntity.class, entity.getId()).toString(), entity.toString());
    assertNull(esem.find(SimpleEntity.class, originalId));
    assertNull(dsem.find(SimpleEntity.class, originalId));
  }
  public void testUpdateFromClient() {
    SimpleEntity entity = new SimpleEntity();
    entity.setString("the string value");
    entity.setDate(new Timestamp(1234567L));
    entity.setInteger(9999);

    ErraiEntityManager esem = csm.getExpectedStateEm();
    ErraiEntityManager dsem = csm.getDesiredStateEm();

    // first persist the expected state
    SimpleEntity originalEntityState = esem.merge(entity);
    esem.flush();
    esem.clear();

    // now make a change and persist the desired state
    SimpleEntity.setId(entity, originalEntityState.getId());
    entity.setString("this has been updated");

    dsem.persist(entity);
    dsem.flush();
    dsem.clear();

    List<SyncRequestOperation<SimpleEntity>> expectedClientRequests =
        new ArrayList<SyncRequestOperation<SimpleEntity>>();
    expectedClientRequests.add(SyncRequestOperation.updated(entity, originalEntityState));

    // the server will respond with confirmation of the update
    List<SyncResponse<SimpleEntity>> fakeServerResponses =
        new ArrayList<SyncResponse<SimpleEntity>>();
    fakeServerResponses.add(new UpdateResponse<SimpleEntity>(entity));
    performColdSync(expectedClientRequests, fakeServerResponses);

    SimpleEntity changedEntityExpected = esem.find(SimpleEntity.class, entity.getId());
    SimpleEntity changedEntityDesired = dsem.find(SimpleEntity.class, entity.getId());
    assertEquals(changedEntityExpected.toString(), entity.toString());
    assertEquals(changedEntityDesired.toString(), entity.toString());
    assertNotSame(changedEntityDesired, changedEntityExpected);
  }
  public void testUpdateFromServer() {
    SimpleEntity newEntity = new SimpleEntity();
    newEntity.setString("the string value");
    newEntity.setDate(new Timestamp(1234567L));
    newEntity.setInteger(9999);

    ErraiEntityManager esem = csm.getExpectedStateEm();
    ErraiEntityManager dsem = csm.getDesiredStateEm();

    // persist this as both the "expected state" from the server and the "desired state" on the
    // client
    SimpleEntity originalEntityState = esem.merge(newEntity);
    esem.flush();
    esem.clear();

    dsem.persist(originalEntityState);
    dsem.flush();
    dsem.clear();

    List<SyncRequestOperation<SimpleEntity>> expectedClientRequests =
        new ArrayList<SyncRequestOperation<SimpleEntity>>();
    expectedClientRequests.add(SyncRequestOperation.unchanged(originalEntityState));

    // now cook up a server response that says something changed
    SimpleEntity.setId(newEntity, originalEntityState.getId());
    newEntity.setString("a new string value");
    newEntity.setInteger(110011);
    List<SyncResponse<SimpleEntity>> fakeServerResponses =
        new ArrayList<SyncResponse<SimpleEntity>>();
    fakeServerResponses.add(new UpdateResponse<SimpleEntity>(newEntity));
    performColdSync(expectedClientRequests, fakeServerResponses);

    SimpleEntity changedEntityExpected = esem.find(SimpleEntity.class, newEntity.getId());
    SimpleEntity changedEntityDesired = dsem.find(SimpleEntity.class, newEntity.getId());
    assertEquals(changedEntityExpected.toString(), newEntity.toString());
    assertEquals(changedEntityDesired.toString(), newEntity.toString());
    assertNotSame(changedEntityDesired, changedEntityExpected);
  }
  // a hybrid of "new entity from server" and "id change from server"
  // the scenario is that we have a local entity with, say, ID 100
  // and the server tells us "here's a new entity. its ID is 100!"
  // so we have to move our existing entity out of the way before accepting the remote one.
  public void testNewEntityWithConflictingIdFromServer() {
    SimpleEntity newRemote = new SimpleEntity();
    newRemote.setString("new entity from server");
    newRemote.setDate(new Timestamp(1234567L));
    newRemote.setInteger(9999);
    SimpleEntity.setId(newRemote, 100L);

    SimpleEntity existingLocal = new SimpleEntity();
    existingLocal.setString("existing local entity");
    existingLocal.setDate(new Timestamp(7654321L));
    existingLocal.setInteger(8888);
    SimpleEntity.setId(existingLocal, 100L);

    ErraiEntityManager esem = csm.getExpectedStateEm();
    ErraiEntityManager dsem = csm.getDesiredStateEm();

    dsem.persist(existingLocal);
    dsem.flush();

    assertNull(esem.find(SimpleEntity.class, existingLocal.getId()));
    assertEquals(
        dsem.find(SimpleEntity.class, existingLocal.getId()).toString(), existingLocal.toString());

    List<SyncRequestOperation<SimpleEntity>> expectedClientRequests =
        new ArrayList<SyncRequestOperation<SimpleEntity>>();
    expectedClientRequests.add(SyncRequestOperation.created(existingLocal)); // note that the mock
    // server will ignore
    // this for the purpose
    // of this test

    List<SyncResponse<SimpleEntity>> fakeServerResponses =
        new ArrayList<SyncResponse<SimpleEntity>>();
    fakeServerResponses.add(new NewRemoteEntityResponse<SimpleEntity>(newRemote));
    performColdSync(expectedClientRequests, fakeServerResponses);

    // now the ID of existingLocal (still managed by dsem) should not be 100 anymore

    assertFalse(
        "Existing id "
            + existingLocal.getId()
            + " should not be the same as new object's ID "
            + newRemote.getId(),
        existingLocal.getId() == newRemote.getId());
    assertSame(existingLocal, dsem.find(SimpleEntity.class, existingLocal.getId()));

    assertEquals(newRemote.toString(), dsem.find(SimpleEntity.class, newRemote.getId()).toString());
    assertEquals(newRemote.toString(), esem.find(SimpleEntity.class, newRemote.getId()).toString());
  }
  public void testIdChangeFromServer() {
    SimpleEntity entity = new SimpleEntity();
    entity.setString("the string value");
    entity.setDate(new Timestamp(1234567L));
    entity.setInteger(9999);

    ErraiEntityManager esem = csm.getExpectedStateEm();
    ErraiEntityManager dsem = csm.getDesiredStateEm();

    SimpleEntity originalLocalState = dsem.merge(entity);
    long originalId = originalLocalState.getId();
    dsem.flush();
    dsem.detach(originalLocalState);

    assertNull(esem.find(SimpleEntity.class, originalId));
    assertEquals(dsem.find(SimpleEntity.class, originalId).toString(), entity.toString());

    // Now change the ID and tell the ClientSyncManager it happened
    long newId = originalLocalState.getId() + 100;
    SimpleEntity.setId(entity, newId);

    List<SyncRequestOperation<SimpleEntity>> expectedClientRequests =
        new ArrayList<SyncRequestOperation<SimpleEntity>>();
    expectedClientRequests.add(SyncRequestOperation.created(originalLocalState));

    List<SyncResponse<SimpleEntity>> fakeServerResponses =
        new ArrayList<SyncResponse<SimpleEntity>>();
    fakeServerResponses.add(new IdChangeResponse<SimpleEntity>(originalId, entity));
    performColdSync(expectedClientRequests, fakeServerResponses);

    assertNull(esem.find(SimpleEntity.class, originalId));
    assertNull(dsem.find(SimpleEntity.class, originalId));

    SimpleEntity changedEntityExpected = esem.find(SimpleEntity.class, newId);
    SimpleEntity changedEntityDesired = dsem.find(SimpleEntity.class, newId);
    assertEquals(changedEntityExpected.toString(), entity.toString());
    assertEquals(changedEntityDesired.toString(), entity.toString());
    assertNotSame(changedEntityDesired, changedEntityExpected);
  }
  public void testNewEntityFromServer() {
    SimpleEntity newEntity = new SimpleEntity();
    newEntity.setString("the string value");
    newEntity.setDate(new Timestamp(1234567L));
    newEntity.setInteger(9999);
    SimpleEntity.setId(newEntity, 88L);

    List<SyncRequestOperation<SimpleEntity>> expectedClientRequests =
        new ArrayList<SyncRequestOperation<SimpleEntity>>();
    // in this case, the client should make an empty request (both persistence contexts are empty)

    List<SyncResponse<SimpleEntity>> fakeServerResponses =
        new ArrayList<SyncResponse<SimpleEntity>>();
    fakeServerResponses.add(new NewRemoteEntityResponse<SimpleEntity>(newEntity));
    performColdSync(expectedClientRequests, fakeServerResponses);

    ErraiEntityManager esem = csm.getExpectedStateEm();
    ErraiEntityManager dsem = csm.getDesiredStateEm();

    SimpleEntity newEntityExpected = esem.find(SimpleEntity.class, newEntity.getId());
    SimpleEntity newEntityDesired = dsem.find(SimpleEntity.class, newEntity.getId());
    assertEquals(newEntityExpected.toString(), newEntity.toString());
    assertEquals(newEntityDesired.toString(), newEntity.toString());
    assertNotSame(
        "Expected State and Desired State instances must be separate",
        newEntityExpected,
        newEntityDesired);
  }