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];