Skip to content
This repository has been archived by the owner on Feb 29, 2020. It is now read-only.

Commit

Permalink
[client][managed][offline] delete errors with operation on cancel and…
Browse files Browse the repository at this point in the history
… collapse
  • Loading branch information
hasankhan committed Nov 18, 2014
1 parent 1698831 commit 372ba61
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -337,10 +337,12 @@ public async Task DeferTableActionAsync(TableAction action)

private async Task TryCancelOperation(MobileServiceTableOperationError error)
{
if (!await this.opQueue.DeleteAsync(error.OperationId, error.OperationVersion))
if (!await this.opQueue.DeleteAsync(error.Id, error.OperationVersion))
{
throw new InvalidOperationException(Resources.SyncError_OperationUpdated);
}
// delete errors for cancelled operation
await this.Store.DeleteAsync(MobileServiceLocalSystemTables.SyncErrors, error.Id);
}

private async Task EnsureInitializedAsync()
Expand Down Expand Up @@ -382,6 +384,8 @@ private Task ExecuteOperationAsync(MobileServiceTableOperation operation, JObjec
if (existing != null)
{
existing.Collapse(operation); // cancel either existing, new or both operation
// delete error for collapsed operation
await this.Store.DeleteAsync(MobileServiceLocalSystemTables.SyncErrors, existing.Id);
if (existing.IsCancelled) // if cancelled we delete it
{
await this.opQueue.DeleteAsync(existing.Id, existing.Version);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,10 @@ private async Task<bool> ExecuteOperationAsync(MobileServiceTableOperation opera
rawResult = content.Item1;
result = content.Item2.ValidItemOrNull();
}
var syncError = new MobileServiceTableOperationError(statusCode,
operation.Id,
var syncError = new MobileServiceTableOperationError(operation.Id,
operation.Version,
operation.Kind,
statusCode,
operation.TableName,
operation.Item,
rawResult,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,6 @@ public class MobileServiceTableOperationError
/// </summary>
public bool Handled { get; set; }

/// <summary>
/// The id of the operation.
/// </summary>
internal string OperationId { get; private set; }

/// <summary>
/// The version of the operation.
/// </summary>
Expand Down Expand Up @@ -75,25 +70,24 @@ public class MobileServiceTableOperationError
/// <summary>
/// Initializes an instance of <see cref="MobileServiceTableOperationError"/>
/// </summary>
/// <param name="status">The HTTP status code returned by server.</param>
/// <param name="operationId">The id of the operation.</param>
/// <param name="id">The id of error that is same as operation id.</param>
/// <param name="operationVersion">The version of the operation.</param>
/// <param name="operationKind">The kind of table operation.</param>
/// <param name="status">The HTTP status code returned by server.</param>
/// <param name="tableName">The name of the remote table.</param>
/// <param name="item">The item associated with the operation.</param>
/// <param name="rawResult">Raw response of the table operation.</param>
/// <param name="result">Response of the table operation.</param>
public MobileServiceTableOperationError(HttpStatusCode? status,
string operationId,
public MobileServiceTableOperationError(string id,
long operationVersion,
MobileServiceTableOperationKind operationKind,
HttpStatusCode? status,
string tableName,
JObject item,
string rawResult,
JObject result)
{
this.Id = Guid.NewGuid().ToString();
this.OperationId = operationId;
this.Id = id;
this.OperationVersion = operationVersion;
this.Status = status;
this.OperationKind = operationKind;
Expand Down Expand Up @@ -139,7 +133,6 @@ internal static void DefineTable(MobileServiceLocalStore store)
{
{ MobileServiceSystemColumns.Id, String.Empty },
{ "httpStatus", 0 },
{ "operationId", String.Empty },
{ "operationVersion", 0 },
{ "operationKind", 0 },
{ "tableName", String.Empty },
Expand All @@ -155,7 +148,6 @@ internal JObject Serialize()
{
{ MobileServiceSystemColumns.Id, this.Id },
{ "httpStatus", this.Status.HasValue ? (int?)this.Status.Value: null },
{ "operationId", this.OperationId },
{ "operationVersion", this.OperationVersion },
{ "operationKind", (int)this.OperationKind },
{ "tableName", this.TableName },
Expand All @@ -173,7 +165,6 @@ internal static MobileServiceTableOperationError Deserialize(JObject obj, Mobile
status = (HttpStatusCode?)obj.Value<int?>("httpStatus");
}
string id = obj.Value<string>(MobileServiceSystemColumns.Id);
string operationId = obj.Value<string>("operationId");
long operationVersion = obj.Value<long?>("operationVersion").GetValueOrDefault();
MobileServiceTableOperationKind operationKind = (MobileServiceTableOperationKind)obj.Value<int>("operationKind");
var tableName = obj.Value<string>("tableName");
Expand All @@ -184,10 +175,10 @@ internal static MobileServiceTableOperationError Deserialize(JObject obj, Mobile
string rawResult = obj.Value<string>("rawResult");
var result = rawResult.ParseToJToken(settings) as JObject;

return new MobileServiceTableOperationError(status,
operationId,
return new MobileServiceTableOperationError(id,
operationVersion,
operationKind,
status,
tableName,
item,
rawResult,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ public void Deserialize_Succeeds()
var serializedError = JObject.Parse(@"
{""id"":""70cf6cc2-5981-4a32-ae6c-249572917a46"",
""httpStatus"": 200,
""operationId"":""80cf6cc2-5981-4a32-ae6c-249572917a46"",
""operationVersion"":123,
""operationKind"":0,
""tableName"":""test"",
Expand All @@ -33,10 +32,9 @@ public void Deserialize_Succeeds()
var operation = MobileServiceTableOperationError.Deserialize(serializedError, this.serializer.SerializerSettings);

Assert.AreEqual(serializedError["id"], operation.Id);
Assert.AreEqual(serializedError["httpStatus"], (int)operation.Status);
Assert.AreEqual(serializedError["operationId"], operation.OperationId);
Assert.AreEqual(serializedError["operationVersion"], operation.OperationVersion);
Assert.AreEqual(serializedError["operationKind"], (int)operation.OperationKind);
Assert.AreEqual(serializedError["httpStatus"], (int)operation.Status);
Assert.AreEqual(serializedError["tableName"], operation.TableName);
Assert.AreEqual(serializedError["tableKind"], (int)operation.TableKind);
Assert.AreEqual(serializedError["item"], operation.Item.ToString(Formatting.None));
Expand All @@ -49,7 +47,6 @@ public void Deserialize_Succeeds_WhenOperationVersionIsNull()
var serializedError = JObject.Parse(@"
{""id"":""70cf6cc2-5981-4a32-ae6c-249572917a46"",
""httpStatus"": 200,
""operationId"":""80cf6cc2-5981-4a32-ae6c-249572917a46"",
""operationVersion"":null,
""operationKind"":0,
""tableName"":""test"",
Expand All @@ -60,10 +57,9 @@ public void Deserialize_Succeeds_WhenOperationVersionIsNull()
var operation = MobileServiceTableOperationError.Deserialize(serializedError, this.serializer.SerializerSettings);

Assert.AreEqual(serializedError["id"], operation.Id);
Assert.AreEqual(serializedError["httpStatus"], (int)operation.Status);
Assert.AreEqual(serializedError["operationId"], operation.OperationId);
Assert.AreEqual(0, operation.OperationVersion);
Assert.AreEqual(serializedError["operationKind"], (int)operation.OperationKind);
Assert.AreEqual(serializedError["httpStatus"], (int)operation.Status);
Assert.AreEqual(serializedError["tableName"], operation.TableName);
Assert.AreEqual(serializedError["tableKind"], (int)operation.TableKind);
Assert.AreEqual(serializedError["item"], operation.Item.ToString(Formatting.None));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,10 @@ public async Task PushAsync_FeatureHeaderPresentWhenRehydrated()
[AsyncTestMethod]
public async Task PushAsync_ReplaysStoredErrors_IfTheyAreInStore()
{
var error = new MobileServiceTableOperationError(HttpStatusCode.PreconditionFailed,
"abc",
var error = new MobileServiceTableOperationError("abc",
1,
MobileServiceTableOperationKind.Update,
HttpStatusCode.PreconditionFailed,
"test",
new JObject(),
"{}",
Expand Down Expand Up @@ -247,7 +247,7 @@ public async Task PushAsync_Succeeds_WithClientWinsPolicy()
}

[AsyncTestMethod]
public async Task CancelAndUpdateItemAsync_UpsertsTheItemInLocalStoreAndDeletesTheOperation()
public async Task CancelAndUpdateItemAsync_UpsertsTheItemInLocalStore_AndDeletesTheOperationAndError()
{
var client = new MobileServiceClient("http://www.test.com");
var store = new MobileServiceLocalStoreMock();
Expand All @@ -258,17 +258,19 @@ public async Task CancelAndUpdateItemAsync_UpsertsTheItemInLocalStoreAndDeletesT
string itemId = "def";
string tableName = "test";


store.TableMap[MobileServiceLocalSystemTables.SyncErrors] = new Dictionary<string, JObject>() { { operationId, new JObject() } };
store.TableMap[MobileServiceLocalSystemTables.OperationQueue].Add(operationId, new JObject());

// operation exists before cancel
Assert.IsNotNull(await store.LookupAsync(MobileServiceLocalSystemTables.OperationQueue, operationId));
// item doesn't exist before upsert
Assert.IsNull(await store.LookupAsync(tableName, itemId));

var error = new MobileServiceTableOperationError(HttpStatusCode.Conflict,
operationId,
var error = new MobileServiceTableOperationError(operationId,
0,
MobileServiceTableOperationKind.Update,
HttpStatusCode.Conflict,
tableName,
item: new JObject() { { "id", itemId } },
rawResult: "{}",
Expand All @@ -279,6 +281,8 @@ public async Task CancelAndUpdateItemAsync_UpsertsTheItemInLocalStoreAndDeletesT

// operation is deleted
Assert.IsNull(await store.LookupAsync(MobileServiceLocalSystemTables.OperationQueue, operationId));
// error is deleted
Assert.IsNull(await store.LookupAsync(MobileServiceLocalSystemTables.SyncErrors, operationId));

JObject upserted = await store.LookupAsync(tableName, itemId);
// item is upserted
Expand All @@ -287,7 +291,7 @@ public async Task CancelAndUpdateItemAsync_UpsertsTheItemInLocalStoreAndDeletesT
}

[AsyncTestMethod]
public async Task CancelAndDiscardItemAsync_DeletesTheItemInLocalStoreAndDeletesTheOperation()
public async Task CancelAndDiscardItemAsync_DeletesTheItemInLocalStore_AndDeletesTheOperationAndError()
{
var client = new MobileServiceClient("http://www.test.com");
var store = new MobileServiceLocalStoreMock();
Expand All @@ -298,6 +302,7 @@ public async Task CancelAndDiscardItemAsync_DeletesTheItemInLocalStoreAndDeletes
string itemId = "def";
string tableName = "test";

store.TableMap[MobileServiceLocalSystemTables.SyncErrors] = new Dictionary<string, JObject>() { { operationId, new JObject() } };
store.TableMap[MobileServiceLocalSystemTables.OperationQueue].Add(operationId, new JObject());
store.TableMap.Add(tableName, new Dictionary<string, JObject>() { { itemId, new JObject() } });

Expand All @@ -306,10 +311,10 @@ public async Task CancelAndDiscardItemAsync_DeletesTheItemInLocalStoreAndDeletes
// item exists before upsert
Assert.IsNotNull(await store.LookupAsync(tableName, itemId));

var error = new MobileServiceTableOperationError(HttpStatusCode.Conflict,
operationId,
var error = new MobileServiceTableOperationError(operationId,
0,
MobileServiceTableOperationKind.Update,
HttpStatusCode.Conflict,
tableName,
item: new JObject() { { "id", itemId } },
rawResult: "{}",
Expand All @@ -319,6 +324,8 @@ public async Task CancelAndDiscardItemAsync_DeletesTheItemInLocalStoreAndDeletes

// operation is deleted
Assert.IsNull(await store.LookupAsync(MobileServiceLocalSystemTables.OperationQueue, operationId));
// error is deleted
Assert.IsNull(await store.LookupAsync(MobileServiceLocalSystemTables.SyncErrors, operationId));

// item is upserted
Assert.IsNull(await store.LookupAsync(tableName, itemId));
Expand Down Expand Up @@ -370,10 +377,10 @@ private async Task TestOperationModifiedException(bool operationExists, Func<Mob
Assert.IsNull(await store.LookupAsync(MobileServiceLocalSystemTables.OperationQueue, operationId));
}

var error = new MobileServiceTableOperationError(HttpStatusCode.Conflict,
operationId,
var error = new MobileServiceTableOperationError(operationId,
1,
MobileServiceTableOperationKind.Update,
HttpStatusCode.Conflict,
tableName,
item: new JObject() { { "id", itemId } },
rawResult: "{}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1154,7 +1154,6 @@ public Task UpdateAsync_Throws_WhenDeleteIsInQueue()
[AsyncTestMethod]
public async Task UpdateAsync_CancelsSecondUpdate_WhenUpdateIsInQueue()
{
var store = new MobileServiceLocalStoreMock();
await this.TestCollapseCancel(firstOperationOnItem1: (table, item1) => table.UpdateAsync(item1),
operationOnItem2: (table, item2) => table.DeleteAsync(item2),
secondOperationOnItem1: (table, item1) => table.UpdateAsync(item1),
Expand All @@ -1176,6 +1175,60 @@ await this.TestCollapseCancel(firstOperationOnItem1: (table, item1) => table.Upd
});
}

[AsyncTestMethod]
public async Task Collapse_DeletesTheError_OnMutualCancel()
{
var store = new MobileServiceLocalStoreMock();
var hijack = new TestHttpHandler();
MobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", hijack);

var item = new StringIdType() { Id = "item1", String = "what?" };

await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());
IMobileServiceSyncTable<StringIdType> table = service.GetSyncTable<StringIdType>();

await table.InsertAsync(item);
Assert.AreEqual(service.SyncContext.PendingOperations, 1L);

string id = store.TableMap[MobileServiceLocalSystemTables.OperationQueue].Values.First().Value<string>("id");
// inject an error to test if it is deleted on collapse
store.TableMap[MobileServiceLocalSystemTables.SyncErrors] = new Dictionary<string, JObject>() { { id, new JObject() } };

await table.DeleteAsync(item);
Assert.AreEqual(service.SyncContext.PendingOperations, 0L);

// error should be deleted
Assert.AreEqual(store.TableMap[MobileServiceLocalSystemTables.SyncErrors].Count, 0);
}

[AsyncTestMethod]
public async Task Collapse_DeletesTheError_OnReplace()
{
var store = new MobileServiceLocalStoreMock();
var hijack = new TestHttpHandler();
MobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", hijack);

var item = new StringIdType() { Id = "item1", String = "what?" };

await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());
IMobileServiceSyncTable<StringIdType> table = service.GetSyncTable<StringIdType>();

await table.InsertAsync(item);
Assert.AreEqual(service.SyncContext.PendingOperations, 1L);

string id = store.TableMap[MobileServiceLocalSystemTables.OperationQueue].Values.First().Value<string>("id");

// inject an error to test if it is deleted on collapse
store.TableMap[MobileServiceLocalSystemTables.SyncErrors] = new Dictionary<string, JObject>() { { id, new JObject() } };

await table.UpdateAsync(item);
Assert.AreEqual(service.SyncContext.PendingOperations, 1L);

// error should be deleted
Assert.AreEqual(store.TableMap[MobileServiceLocalSystemTables.SyncErrors].Count, 0);
}


[AsyncTestMethod]
public async Task UpdateAsync_CancelsSecondUpdate_WhenInsertIsInQueue()
{
Expand All @@ -1198,7 +1251,6 @@ await this.TestCollapseCancel(firstOperationOnItem1: (table, item1) => table.Ins
var op = queue.Values.Single(o => o.Value<string>("itemId") == "item1");
Assert.AreEqual(op.Value<long>("version"), 2L);
});

}

[AsyncTestMethod]
Expand Down

0 comments on commit 372ba61

Please sign in to comment.