diff --git a/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Resources.Designer.cs b/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Resources.Designer.cs
index 1d5c04eb2..3d085b60a 100644
--- a/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Resources.Designer.cs
+++ b/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Resources.Designer.cs
@@ -763,24 +763,6 @@ internal static string ODataExpressionParser_UnterminatedStringLiteral {
}
}
- ///
- /// Looks up a localized string similar to The assembly {0} does not define a type that implements the {1} interface..
- ///
- internal static string Platform_AssemblyDidNotHaveType {
- get {
- return ResourceManager.GetString("Platform_AssemblyDidNotHaveType", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Third-party provider authentication requires user interface components and is not supported with the platform-specific assembly {0}. To authenticate users with a third-party provider, ensure that the project references the platform-specific UI assembly {0}.UI..
- ///
- internal static string Platform_AssemblyDoesNotSupportLogin {
- get {
- return ResourceManager.GetString("Platform_AssemblyDoesNotSupportLogin", resourceCulture);
- }
- }
-
///
/// Looks up a localized string similar to A Microsoft Azure Mobile Services assembly for the current platform was not found. Ensure that the current project references both {0} and the following platform-specific assembly: {1}..
///
@@ -799,6 +781,15 @@ internal static string Platform_InstantiationFailed {
}
}
+ ///
+ /// Looks up a localized string similar to The key '{0}' is reserved and cannot be specified as a query parameter..
+ ///
+ internal static string Pull_Cannot_Use_Reserved_Key {
+ get {
+ return ResourceManager.GetString("Pull_Cannot_Use_Reserved_Key", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Valid XML is required for any template without a raw header..
///
diff --git a/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Resources.resx b/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Resources.resx
index 5a9d0763e..d960ef613 100644
--- a/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Resources.resx
+++ b/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Resources.resx
@@ -426,4 +426,7 @@
This page must be called from the type '{0}'.
+
+ The key '{0}' is reserved and cannot be specified as a query parameter.
+
\ No newline at end of file
diff --git a/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Table/MobileServiceTable.cs b/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Table/MobileServiceTable.cs
index 300fec138..645bf66bb 100644
--- a/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Table/MobileServiceTable.cs
+++ b/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Table/MobileServiceTable.cs
@@ -34,7 +34,7 @@ internal class MobileServiceTable : IMobileServiceTable
///
/// The name of the system properties query string parameter
///
- private const string SystemPropertiesQueryParameterName = "__systemproperties";
+ public const string SystemPropertiesQueryParameterName = "__systemproperties";
///
/// The name of the include deleted query string parameter
diff --git a/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Table/Sync/MobileServiceSyncContext.cs b/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Table/Sync/MobileServiceSyncContext.cs
index ffdd349cc..bc72a0d70 100644
--- a/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Table/Sync/MobileServiceSyncContext.cs
+++ b/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Table/Sync/MobileServiceSyncContext.cs
@@ -179,6 +179,16 @@ public async Task PullAsync(string tableName, MobileServiceTableKind tableKind,
{
await this.EnsureInitializedAsync();
+ if (parameters.Keys.Any(k => k.Equals(MobileServiceTable.IncludeDeletedParameterName, StringComparison.OrdinalIgnoreCase)))
+ {
+ throw new ArgumentException(Resources.Pull_Cannot_Use_Reserved_Key.FormatInvariant(MobileServiceTable.IncludeDeletedParameterName));
+ }
+
+ if (parameters.Keys.Any(k => k.Equals(MobileServiceTable.SystemPropertiesQueryParameterName, StringComparison.OrdinalIgnoreCase)))
+ {
+ throw new ArgumentException(Resources.Pull_Cannot_Use_Reserved_Key.FormatInvariant(MobileServiceTable.SystemPropertiesQueryParameterName));
+ }
+
var table = await this.GetTable(tableName);
var queryDescription = MobileServiceTableQueryDescription.Parse(this.client.ApplicationUri, tableName, query);
diff --git a/sdk/Managed/test/Microsoft.WindowsAzure.MobileServices.Test/UnitTests/Table/Sync/MobileServiceSyncTable.Generic.Test.cs b/sdk/Managed/test/Microsoft.WindowsAzure.MobileServices.Test/UnitTests/Table/Sync/MobileServiceSyncTable.Generic.Test.cs
index a906acae0..030330e9a 100644
--- a/sdk/Managed/test/Microsoft.WindowsAzure.MobileServices.Test/UnitTests/Table/Sync/MobileServiceSyncTable.Generic.Test.cs
+++ b/sdk/Managed/test/Microsoft.WindowsAzure.MobileServices.Test/UnitTests/Table/Sync/MobileServiceSyncTable.Generic.Test.cs
@@ -792,25 +792,25 @@ private static async Task TestIncrementalPull(MobileServiceLocalStoreMock store,
}
[AsyncTestMethod]
- public async Task PullAsync_OverridesStoreSystemProperties_WhenProvidedInParameters()
+ public async Task PullAsync_Throws_IfSystemPropertiesProvidedInParameters()
{
- await TestPullQueryOverride(new Dictionary()
+ await this.TestPullQueryOverrideThrows(new Dictionary()
{
{ "__systemProperties", "createdAt" },
{ "param1", "val1" }
},
- "?$skip=0&$top=50&__systemProperties=createdAt¶m1=val1&__includeDeleted=true");
+ "The key '__systemProperties' is reserved and cannot be specified as a query parameter.");
}
[AsyncTestMethod]
- public async Task PullAsync_OverridesIncludeDeleted_WhenProvidedInParameters()
+ public async Task PullAsync_Throws_IfIncludeDeletedProvidedInParameters()
{
- await TestPullQueryOverride(new Dictionary()
+ await this.TestPullQueryOverrideThrows(new Dictionary()
{
{ "__includeDeleted", "false" },
{ "param1", "val1" }
},
- "?$skip=0&$top=50&__includeDeleted=false¶m1=val1&__systemproperties=__version%2C__deleted");
+ "The key '__includeDeleted' is reserved and cannot be specified as a query parameter.");
}
[AsyncTestMethod]
@@ -826,26 +826,18 @@ public async Task PullAsync_Throws_WhenQueryKeyIsInvalid()
await ThrowsAsync(() => table.PullAsync("asd_^^234", table.CreateQuery(), CancellationToken.None));
}
- private static async Task TestPullQueryOverride(IDictionary parameters, string uriQuery)
+ private async Task TestPullQueryOverrideThrows(IDictionary parameters, string errorMessage)
{
- var hijack = new TestHttpHandler();
- hijack.OnSendingRequest = req =>
- {
- Assert.AreEqual(req.RequestUri.Query, uriQuery);
- return Task.FromResult(req);
- };
- hijack.AddResponseContent("[]"); // for pull
-
var store = new MobileServiceLocalStoreMock();
- IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", hijack);
+ IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...");
await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());
IMobileServiceSyncTable table = service.GetSyncTable();
var query = table.CreateQuery()
.WithParameters(parameters);
- await table.PullAsync(null, query, cancellationToken: CancellationToken.None);
- Assert.AreEqual(hijack.Requests.Count, 1);
+ var ex = await ThrowsAsync(() => table.PullAsync(null, query, cancellationToken: CancellationToken.None));
+ Assert.AreEqual(errorMessage, ex.Message);
}
[AsyncTestMethod]
diff --git a/sdk/iOS/src/MSSyncContext.m b/sdk/iOS/src/MSSyncContext.m
index 9199c12db..fb3a9588d 100644
--- a/sdk/iOS/src/MSSyncContext.m
+++ b/sdk/iOS/src/MSSyncContext.m
@@ -307,9 +307,11 @@ - (void) pullWithQuery:(MSQuery *)query completion:(MSSyncBlock)completion;
error = [self errorWithDescription:@"Use of includeTotalCount is not supported in pullWithQuery:"
andErrorCode:MSInvalidParameter];
}
- else if (query.parameters) {
- error = [self errorWithDescription:@"Use of parameters is not supported in pullWithQuery:"
- andErrorCode:MSInvalidParameter];
+ else if ([MSSyncContext dictionary:query.parameters containsCaseInsensitiveKey:@"__includedeleted"]){
+ error = [self errorWithDescription:@"Use of '__includeDeleted' is not supported in pullWithQuery parameters:" andErrorCode:MSInvalidParameter];
+ }
+ else if ([MSSyncContext dictionary:query.parameters containsCaseInsensitiveKey:@"__systemproperties"]) {
+ error = [self errorWithDescription:@"Use of '__systemProperties' is not supported in pullWithQuery parameters:" andErrorCode:MSInvalidParameter];
}
else if (query.syncTable) {
// Otherwise we convert the sync table to a normal table
@@ -330,15 +332,22 @@ - (void) pullWithQuery:(MSQuery *)query completion:(MSSyncBlock)completion;
return;
}
- // Get the required system properties from the server
+ // Get the required system properties from the Store
if ([self.dataSource respondsToSelector:@selector(systemPropetiesForTable:)]) {
query.table.systemProperties = [self.dataSource systemPropetiesForTable:query.table.name];
} else {
query.table.systemProperties = MSSystemPropertyVersion;
}
- // A pull should always include deleted records
- query.parameters = @{@"__includeDeleted" : @YES };
+ // add __includeDeleted
+ if (!query.parameters) {
+ query.parameters = @{@"__includeDeleted" : @YES};
+ } else {
+ NSMutableDictionary *mutableParameters = [query.parameters mutableCopy];
+ [mutableParameters setObject:@YES forKey:@"__includeDeleted"];
+ query.parameters = mutableParameters;
+ }
+
query.table.systemProperties |= MSSystemPropertyDeleted;
// Begin the actual pull request
@@ -458,6 +467,15 @@ - (void) purgeWithQuery:(MSQuery *)query completion:(MSSyncBlock)completion
});
}
++ (BOOL) dictionary:(NSDictionary *)dictionary containsCaseInsensitiveKey:(NSString *)key
+{
+ for (NSString *object in dictionary.allKeys) {
+ if ([object caseInsensitiveCompare:key] == NSOrderedSame) {
+ return YES;
+ }
+ }
+ return NO;
+}
# pragma mark * NSError helpers
diff --git a/sdk/iOS/test/MSSyncTableTests.m b/sdk/iOS/test/MSSyncTableTests.m
index 51bacf15b..1ea0745f1 100644
--- a/sdk/iOS/test/MSSyncTableTests.m
+++ b/sdk/iOS/test/MSSyncTableTests.m
@@ -915,6 +915,94 @@ -(void) testPullAddsProperFeaturesHeader
XCTAssertTrue([self waitForTest:30.0], @"Test timed out.");
}
+-(void) testPullWithCustomParameters
+{
+ NSString* stringData = @"[{\"id\": \"one\", \"text\":\"first item\"},{\"id\": \"two\", \"text\":\"second item\"}]";
+ MSTestFilter *testFilter = [MSTestFilter testFilterWithStatusCode:200 data:stringData];
+
+ __block NSURLRequest *actualRequest = nil;
+ testFilter.onInspectRequest = ^(NSURLRequest *request) {
+ actualRequest = request;
+ return request;
+ };
+
+ offline.upsertCalls = 0;
+
+ MSClient *filteredClient = [client clientWithFilter:testFilter];
+ MSSyncTable *todoTable = [filteredClient syncTableWithName:TodoTableNoVersion];
+ MSQuery *query = [[MSQuery alloc] initWithSyncTable:todoTable];
+ query.parameters = @{@"mykey": @"myvalue"};
+
+ [todoTable pullWithQuery:query completion:^(NSError *error) {
+ XCTAssertNil(error, @"Error found: %@", error.description);
+ XCTAssertEqual((int)offline.upsertCalls, 1, @"Unexpected number of upsert calls");
+ XCTAssertEqual((int)offline.upsertedItems, 2, @"Unexpected number of upsert calls");
+ done = YES;
+ }];
+
+ XCTAssertTrue([self waitForTest:30.0], @"Test timed out.");
+
+ XCTAssertEqualObjects(actualRequest.URL.absoluteString, @"https://someUrl/tables/TodoNoVersion?$inlinecount=none&mykey=myvalue&__includeDeleted=1&__systemProperties=__deleted");
+}
+
+-(void) testPullWithIncludeDeletedFails
+{
+ NSString* stringData = @"[{\"id\": \"one\", \"text\":\"first item\"},{\"id\": \"two\", \"text\":\"second item\"}]";
+ MSTestFilter *testFilter = [MSTestFilter testFilterWithStatusCode:200 data:stringData];
+
+ __block NSURLRequest *actualRequest = nil;
+ testFilter.onInspectRequest = ^(NSURLRequest *request) {
+ actualRequest = request;
+ return request;
+ };
+
+ offline.upsertCalls = 0;
+
+ MSClient *filteredClient = [client clientWithFilter:testFilter];
+ MSSyncTable *todoTable = [filteredClient syncTableWithName:TodoTableNoVersion];
+ MSQuery *query = [[MSQuery alloc] initWithSyncTable:todoTable];
+ query.parameters = @{@"__includeDeleted": @NO, @"mykey": @"myvalue"};
+
+ [todoTable pullWithQuery:query completion:^(NSError *error) {
+ XCTAssertNotNil(error);
+ XCTAssertEqual(error.code, MSInvalidParameter);
+ XCTAssertEqual((int)offline.upsertCalls, 0, @"Unexpected number of upsert calls");
+ XCTAssertEqual((int)offline.upsertedItems, 0, @"Unexpected number of upsert calls");
+ done = YES;
+ }];
+
+ XCTAssertTrue([self waitForTest:30.0], @"Test timed out.");
+}
+
+-(void) testPullWithSystemPropertiesFails
+{
+ NSString* stringData = @"[{\"id\": \"one\", \"text\":\"first item\"},{\"id\": \"two\", \"text\":\"second item\"}]";
+ MSTestFilter *testFilter = [MSTestFilter testFilterWithStatusCode:200 data:stringData];
+
+ __block NSURLRequest *actualRequest = nil;
+ testFilter.onInspectRequest = ^(NSURLRequest *request) {
+ actualRequest = request;
+ return request;
+ };
+
+ offline.upsertCalls = 0;
+
+ MSClient *filteredClient = [client clientWithFilter:testFilter];
+ MSSyncTable *todoTable = [filteredClient syncTableWithName:TodoTableNoVersion];
+ MSQuery *query = [[MSQuery alloc] initWithSyncTable:todoTable];
+ query.parameters = @{@"__systemProperties": @"__createdAt,__somethingRandom"};
+
+ [todoTable pullWithQuery:query completion:^(NSError *error) {
+ XCTAssertNotNil(error);
+ XCTAssertEqual(error.code, MSInvalidParameter);
+ XCTAssertEqual((int)offline.upsertCalls, 0, @"Unexpected number of upsert calls");
+ XCTAssertEqual((int)offline.upsertedItems, 0, @"Unexpected number of upsert calls");
+ done = YES;
+ }];
+
+ XCTAssertTrue([self waitForTest:30.0], @"Test timed out.");
+}
+
-(void) testPushAddsProperFeaturesHeader
{
NSString* stringData = @"{\"id\": \"test1\", \"text\":\"test name\"}";
@@ -1053,7 +1141,6 @@ -(void) testPurgeWithPendingOperationsFails
XCTAssertTrue([self waitForTest:30.1], @"Test timed out.");
}
-
#pragma mark * Async Test Helper Method
diff --git a/sdk/iOS/test/MSTableFuncTests.m b/sdk/iOS/test/MSTableFuncTests.m
index c89bd183e..cce03a96e 100644
--- a/sdk/iOS/test/MSTableFuncTests.m
+++ b/sdk/iOS/test/MSTableFuncTests.m
@@ -27,8 +27,8 @@ -(void) setUp
// application key for the Windows Mobile Azure Service below.
MSClient *client = [MSClient
- clientWithApplicationURLString:@""
- applicationKey:@""];
+ clientWithApplicationURLString:@""
+ applicationKey:@""];
XCTAssertTrue([client.applicationURL.description hasPrefix:@"https://"], @"The functional tests are currently disabled.");
self.continueAfterFailure = YES;
diff --git a/sdk/iOS/test/WindowsAzureMobileServicesFunctionalTests.m b/sdk/iOS/test/WindowsAzureMobileServicesFunctionalTests.m
index 6d01278b4..c2094c26d 100644
--- a/sdk/iOS/test/WindowsAzureMobileServicesFunctionalTests.m
+++ b/sdk/iOS/test/WindowsAzureMobileServicesFunctionalTests.m
@@ -32,8 +32,8 @@ - (void) setUp
// application key for the Windows Mobile Azure Service below.
client = [MSClient
- clientWithApplicationURLString:@""
- applicationKey:@""];
+ clientWithApplicationURLString:@""
+ applicationKey:@""];
XCTAssertTrue([client.applicationURL.description hasPrefix:@"https://"], @"The functional tests are currently disabled.");
@@ -126,7 +126,7 @@ -(void) testCreateAndQueryTodoItem
NSDictionary *item3 = @{ @"text":@"ItemB", @"complete": @(NO) };
NSArray *items = @[item1,item2, item3];
- id query7AfterQuery6 = ^(NSArray *items, NSInteger totalCount, NSError *error) {
+ id query7AfterQuery6 = ^(MSQueryResult *result, NSError *error) {
// Check for an error
if (error) {
@@ -134,13 +134,13 @@ -(void) testCreateAndQueryTodoItem
done = YES;
}
- XCTAssertTrue(items.count == 2, @"items.count was: %lu", (unsigned long)items.count);
- XCTAssertTrue(totalCount == 3, @"totalCount was: %ld", (long)totalCount);
+ XCTAssertTrue(result.items.count == 2, @"items.count was: %lu", (unsigned long)result.items.count);
+ XCTAssertTrue(result.totalCount == 3, @"totalCount was: %ld", (long)result.totalCount);
[todoTable deleteAllItemsWithCompletion:^(NSError *error) { done = YES; }];
};
- id query6AfterQuery5 = ^(NSArray *items, NSInteger totalCount, NSError *error) {
+ id query6AfterQuery5 = ^(MSQueryResult *result, NSError *error) {
// Check for an error
if (error) {
@@ -148,8 +148,8 @@ -(void) testCreateAndQueryTodoItem
done = YES;
}
- XCTAssertTrue(items.count == 2, @"items.count was: %lu", (unsigned long)items.count);
- XCTAssertTrue(totalCount == 3, @"totalCount was: %ld", (long)totalCount);
+ XCTAssertTrue(result.items.count == 2, @"items.count was: %lu", (unsigned long)result.items.count);
+ XCTAssertTrue(result.totalCount == 3, @"totalCount was: %ld", (long)result.totalCount);
MSQuery *query = [todoTable query];
query.fetchOffset = 1;
@@ -157,7 +157,7 @@ -(void) testCreateAndQueryTodoItem
[query readWithCompletion:query7AfterQuery6];
};
- id query5AfterQuery4 = ^(NSArray *items, NSInteger totalCount, NSError *error) {
+ id query5AfterQuery4 = ^(MSQueryResult *result, NSError *error) {
// Check for an error
if (error) {
@@ -165,8 +165,8 @@ -(void) testCreateAndQueryTodoItem
done = YES;
}
- XCTAssertTrue(items.count == 3, @"items.count was: %lu", (unsigned long)items.count);
- XCTAssertTrue(totalCount == -1, @"totalCount was: %ld", (long)totalCount);
+ XCTAssertTrue(result.items.count == 3, @"items.count was: %lu", (unsigned long)result.items.count);
+ XCTAssertTrue(result.totalCount == -1, @"totalCount was: %ld", (long)result.totalCount);
[todoTable readWithQueryString:@"$top=2&$inlinecount=allpages"
completion:query6AfterQuery5];
@@ -185,7 +185,7 @@ -(void) testCreateAndQueryTodoItem
[todoTable readWithQueryString:nil completion:query5AfterQuery4];
};
- id query3AfterQuery2 = ^(NSArray *items, NSInteger totalCount, NSError *error) {
+ id query3AfterQuery2 = ^(MSQueryResult *result, NSError *error) {
// Check for an error
if (error) {
@@ -193,14 +193,14 @@ -(void) testCreateAndQueryTodoItem
done = YES;
}
- XCTAssertTrue(items.count == 1, @"items.count was: %lu", (unsigned long)items.count);
- XCTAssertTrue(totalCount == -1, @"totalCount was: %ld", (long)totalCount);
+ XCTAssertTrue(result.items.count == 1, @"items.count was: %lu", (unsigned long)result.items.count);
+ XCTAssertTrue(result.totalCount == -1, @"totalCount was: %ld", (long)result.totalCount);
- [todoTable readWithId:[items[0] valueForKey:@"id"]
+ [todoTable readWithId:[result.items[0] valueForKey:@"id"]
completion:query4AfterQuery3];
};
- id query2AfterQuery1 = ^(NSArray *items, NSInteger totalCount, NSError *error) {
+ id query2AfterQuery1 = ^(MSQueryResult *result, NSError *error) {
// Check for an error
if (error) {
@@ -208,8 +208,8 @@ -(void) testCreateAndQueryTodoItem
done = YES;
}
- XCTAssertTrue(items.count == 2, @"items.count was: %lu", (unsigned long)items.count);
- XCTAssertTrue(totalCount == -1, @"totalCount was: %ld", (long)totalCount);
+ XCTAssertTrue(result.items.count == 2, @"items.count was: %lu", (unsigned long)result.items.count);
+ XCTAssertTrue(result.totalCount == -1, @"totalCount was: %ld", (long)result.totalCount);
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"text ENDSWITH 'B' AND complete == TRUE"];
[todoTable readWithPredicate:predicate completion:query3AfterQuery2];