public void ThrowsWhenArgumentsAreNull(bool hasEntity, bool hasReason) { var entity = hasEntity ? new TestModel(-1, SyncStatus.SyncNeeded) : (IThreadSafeTestModel)null; Exception reason = hasReason ? new ApiException(request, response, "Test.") : null; var state = new UnsyncableEntityState <IThreadSafeTestModel>(dataSource, TestModel.Unsyncable); Action callingStart = () => state.Start((reason, entity)).SingleAsync().Wait(); callingStart.Should().Throw <ArgumentNullException>(); }
public void ThrowsWhenTheReasonExceptionIsNotAnApiException() { var state = new UnsyncableEntityState <IThreadSafeTestModel>(dataSource, TestModel.Unsyncable); var exception = new TestException(); Action callingStart = () => state.Start( (exception, new TestModel(1, SyncStatus.SyncNeeded))).SingleAsync().Wait(); callingStart.Should().Throw <TestException>().Where(e => e == exception); }
private static IStateResult configureCreateOnlyPush <TModel, TDatabase, TThreadsafe>( ITransitionConfigurator transitions, IStateResult entryPoint, IDataSource <TThreadsafe, TDatabase> dataSource, IAnalyticsService analyticsService, ICreatingApiClient <TModel> creatingApi, Func <TModel, TThreadsafe> toClean, Func <TThreadsafe, string, TThreadsafe> toUnsyncable, ITogglApi api, IScheduler scheduler, IObservable <Unit> delayCancellation) where TModel : IIdentifiable, ILastChangedDatable where TDatabase : class, TModel, IDatabaseSyncable where TThreadsafe : class, TDatabase, IThreadSafeModel { var rnd = new Random(); var apiDelay = new RetryDelayService(rnd); var statusDelay = new RetryDelayService(rnd); var push = new PushState <TDatabase, TThreadsafe>(dataSource); var pushOne = new PushOneEntityState <TThreadsafe>(); var create = new CreateEntityState <TModel, TDatabase, TThreadsafe>(creatingApi, dataSource, analyticsService, toClean); var tryResolveClientError = new TryResolveClientErrorState <TThreadsafe>(); var unsyncable = new UnsyncableEntityState <TThreadsafe>(dataSource, toUnsyncable); var checkServerStatus = new CheckServerStatusState(api, scheduler, apiDelay, statusDelay, delayCancellation); var finished = new ResetAPIDelayState(apiDelay); transitions.ConfigureTransition(entryPoint, push); transitions.ConfigureTransition(push.PushEntity, pushOne); transitions.ConfigureTransition(pushOne.CreateEntity, create); transitions.ConfigureTransition(pushOne.UpdateEntity, new InvalidTransitionState($"Updating is not supported for {typeof(TModel).Name} during Push sync.")); transitions.ConfigureTransition(pushOne.DeleteEntity, new InvalidTransitionState($"Deleting is not supported for {typeof(TModel).Name} during Push sync.")); transitions.ConfigureTransition(pushOne.DeleteEntityLocally, new InvalidTransitionState($"Deleting locally is not supported for {typeof(TModel).Name} during Push sync.")); transitions.ConfigureTransition(create.ClientError, tryResolveClientError); transitions.ConfigureTransition(create.ServerError, checkServerStatus); transitions.ConfigureTransition(create.UnknownError, checkServerStatus); transitions.ConfigureTransition(tryResolveClientError.UnresolvedTooManyRequests, checkServerStatus); transitions.ConfigureTransition(tryResolveClientError.Unresolved, unsyncable); transitions.ConfigureTransition(checkServerStatus.Retry, checkServerStatus); transitions.ConfigureTransition(checkServerStatus.ServerIsAvailable, push); transitions.ConfigureTransition(create.EntityChanged, create); transitions.ConfigureTransition(create.Finished, finished); transitions.ConfigureTransition(finished.Continue, push); return(push.NothingToPush); }
public async Task TheSyncStatusOfTheEntityChangesToSyncFailedWhenEverythingWorks() { var entity = new TestModel(1, SyncStatus.SyncNeeded); var state = new UnsyncableEntityState <IThreadSafeTestModel>(dataSource, TestModel.Unsyncable); prepareBatchUpdate(entity); var transition = await state.Start((new BadRequestException(request, response), entity)).SingleAsync(); var unsyncableEntity = ((Transition <IThreadSafeTestModel>)transition).Parameter; unsyncableEntity.SyncStatus.Should().Be(SyncStatus.SyncFailed); }
public void ThrowsWhenDatabaseOperationFails() { var state = new UnsyncableEntityState <IThreadSafeTestModel>(dataSource, TestModel.Unsyncable); dataSource .OverwriteIfOriginalDidNotChange(null, null) .ReturnsForAnyArgs(_ => throw new TestException()); Action callingStart = () => state.Start( (new ApiException(request, response, "Test."), new TestModel(1, SyncStatus.SyncNeeded))).SingleAsync().Wait(); callingStart.Should().Throw <TestException>(); }
private static IStateResult configurePushSingleton <TModel, TThreadsafe>( TransitionHandlerProvider transitions, IStateResult entryPoint, ISingletonDataSource <TThreadsafe> dataSource, IUpdatingApiClient <TModel> updatingApi, Func <TModel, TThreadsafe> toClean, Func <TThreadsafe, string, TThreadsafe> toUnsyncable, ITogglApi api, IScheduler scheduler, IObservable <Unit> delayCancellation) where TModel : class where TThreadsafe : class, TModel, IThreadSafeModel, IDatabaseSyncable { var rnd = new Random(); var apiDelay = new RetryDelayService(rnd); var statusDelay = new RetryDelayService(rnd); var push = new PushSingleState <TThreadsafe>(dataSource); var pushOne = new PushOneEntityState <TThreadsafe>(); var update = new UpdateEntityState <TModel, TThreadsafe>(updatingApi, dataSource, toClean); var tryResolveClientError = new TryResolveClientErrorState <TThreadsafe>(); var unsyncable = new UnsyncableEntityState <TThreadsafe>(dataSource, toUnsyncable); var checkServerStatus = new CheckServerStatusState(api, scheduler, apiDelay, statusDelay, delayCancellation); var finished = new ResetAPIDelayState(apiDelay); transitions.ConfigureTransition(entryPoint, push.Start); transitions.ConfigureTransition(push.PushEntity, pushOne.Start); transitions.ConfigureTransition(pushOne.UpdateEntity, update.Start); transitions.ConfigureTransition(pushOne.CreateEntity, new InvalidTransitionState($"Creating is not supported for {typeof(TModel).Name} during Push sync.").Start); transitions.ConfigureTransition(pushOne.DeleteEntity, new InvalidTransitionState($"Deleting is not supported for {typeof(TModel).Name} during Push sync.").Start); transitions.ConfigureTransition(pushOne.DeleteEntityLocally, new InvalidTransitionState($"Deleting locally is not supported for {typeof(TModel).Name} during Push sync.").Start); transitions.ConfigureTransition(update.ClientError, tryResolveClientError.Start); transitions.ConfigureTransition(update.ServerError, checkServerStatus.Start); transitions.ConfigureTransition(update.UnknownError, checkServerStatus.Start); transitions.ConfigureTransition(tryResolveClientError.UnresolvedTooManyRequests, checkServerStatus.Start); transitions.ConfigureTransition(tryResolveClientError.Unresolved, unsyncable.Start); transitions.ConfigureTransition(checkServerStatus.Retry, checkServerStatus.Start); transitions.ConfigureTransition(checkServerStatus.ServerIsAvailable, push.Start); transitions.ConfigureTransition(update.UpdatingSucceeded, finished.Start); transitions.ConfigureTransition(finished.Continue, push.Start); return(push.NothingToPush); }
public async Task TheUpdatedEntityHasTheSameIdAsTheOriginalEntity() { var entity = new TestModel(1, SyncStatus.SyncNeeded); var state = new UnsyncableEntityState <IThreadSafeTestModel>(dataSource, TestModel.Unsyncable); prepareBatchUpdate(entity); await state.Start((new BadRequestException(request, response), entity)).SingleAsync(); await dataSource .Received() .OverwriteIfOriginalDidNotChange( Arg.Is(entity), Arg.Is <IThreadSafeTestModel>(updatedEntity => updatedEntity.Id == entity.Id)); }
public void TheErrorMessageMatchesTheMessageFromTheReasonException(NonNull <string> message) { var entity = new TestModel(1, SyncStatus.SyncNeeded); var response = Substitute.For <IResponse>(); response.RawData.Returns(message.Get); var reason = new BadRequestException(request, response); var state = new UnsyncableEntityState <IThreadSafeTestModel>(dataSource, TestModel.Unsyncable); prepareBatchUpdate(entity); var transition = state.Start((reason, entity)).SingleAsync().Wait(); var unsyncableEntity = ((Transition <IThreadSafeTestModel>)transition).Parameter; unsyncableEntity.LastSyncErrorMessage.Should().Be(message.Get); }
public void TheOnlyThingThatChangesInTheUnsyncableEntityIsTheSyncStatusAndLastSyncErrorMessage() { var entity = new TestModel(1, SyncStatus.SyncNeeded); var reason = new BadRequestException(request, response); var state = new UnsyncableEntityState <IThreadSafeTestModel>(dataSource, TestModel.Unsyncable); prepareBatchUpdate(entity); var transition = state.Start((reason, entity)).SingleAsync().Wait(); var unsyncableEntity = ((Transition <IThreadSafeTestModel>)transition).Parameter; entity.Should().BeEquivalentTo(unsyncableEntity, options => options.IncludingProperties() .Excluding(x => x.LastSyncErrorMessage) .Excluding(x => x.SyncStatus)); }
private static IStateResult configurePush <TModel, TDatabase, TThreadsafe>( TransitionHandlerProvider transitions, IStateResult entryPoint, IDataSource <TThreadsafe, TDatabase> dataSource, ICreatingApiClient <TModel> creatingApi, IUpdatingApiClient <TModel> updatingApi, IDeletingApiClient <TModel> deletingApi, Func <TModel, TThreadsafe> toClean, Func <TThreadsafe, string, TThreadsafe> toUnsyncable, ITogglApi api, IRetryDelayService apiDelay, IScheduler scheduler, IObservable <Unit> delayCancellation) where TModel : class, IIdentifiable, ILastChangedDatable where TDatabase : class, TModel, IDatabaseSyncable where TThreadsafe : class, TDatabase, IThreadSafeModel { var rnd = new Random(); var statusDelay = new RetryDelayService(rnd); var push = new PushState <TDatabase, TThreadsafe>(dataSource); var pushOne = new PushOneEntityState <TThreadsafe>(); var create = new CreateEntityState <TModel, TThreadsafe>(creatingApi, dataSource, toClean); var update = new UpdateEntityState <TModel, TThreadsafe>(updatingApi, dataSource, toClean); var delete = new DeleteEntityState <TModel, TDatabase, TThreadsafe>(deletingApi, dataSource); var deleteLocal = new DeleteLocalEntityState <TDatabase, TThreadsafe>(dataSource); var tryResolveClientError = new TryResolveClientErrorState <TThreadsafe>(); var unsyncable = new UnsyncableEntityState <TThreadsafe>(dataSource, toUnsyncable); var checkServerStatus = new CheckServerStatusState(api, scheduler, apiDelay, statusDelay, delayCancellation); var finished = new ResetAPIDelayState(apiDelay); transitions.ConfigureTransition(entryPoint, push); transitions.ConfigureTransition(push.PushEntity, pushOne); transitions.ConfigureTransition(pushOne.CreateEntity, create); transitions.ConfigureTransition(pushOne.UpdateEntity, update); transitions.ConfigureTransition(pushOne.DeleteEntity, delete); transitions.ConfigureTransition(pushOne.DeleteEntityLocally, deleteLocal); transitions.ConfigureTransition(create.ClientError, tryResolveClientError); transitions.ConfigureTransition(update.ClientError, tryResolveClientError); transitions.ConfigureTransition(delete.ClientError, tryResolveClientError); transitions.ConfigureTransition(create.ServerError, checkServerStatus); transitions.ConfigureTransition(update.ServerError, checkServerStatus); transitions.ConfigureTransition(delete.ServerError, checkServerStatus); transitions.ConfigureTransition(create.UnknownError, checkServerStatus); transitions.ConfigureTransition(update.UnknownError, checkServerStatus); transitions.ConfigureTransition(delete.UnknownError, checkServerStatus); transitions.ConfigureTransition(tryResolveClientError.UnresolvedTooManyRequests, checkServerStatus); transitions.ConfigureTransition(tryResolveClientError.Unresolved, unsyncable); transitions.ConfigureTransition(checkServerStatus.Retry, checkServerStatus); transitions.ConfigureTransition(checkServerStatus.ServerIsAvailable, push); transitions.ConfigureTransition(create.CreatingFinished, finished); transitions.ConfigureTransition(update.UpdatingSucceeded, finished); transitions.ConfigureTransition(delete.DeletingFinished, finished); transitions.ConfigureTransition(deleteLocal.Deleted, finished); transitions.ConfigureTransition(deleteLocal.DeletingFailed, finished); transitions.ConfigureTransition(finished.Continue, push); return(push.NothingToPush); }