public async Task TestConcurrentAccountRequestsAsync() { var logger = new MockLogger(); var cosmosState = new MockCosmosState(logger); var client = new MockImageGalleryClient(cosmosState, logger); await client.InitializeCosmosDbAsync(); // Try create a new account, and wait for it to be created before proceeding with the test. var account = new Account("0", "alice", "*****@*****.**"); var result = await client.CreateAccountAsync(account); Assert.IsTrue(result); var updatedAccount = new Account("0", "alice", "*****@*****.**"); // Try update the account and delete it concurrently, which can cause a data race and a bug. var updateTask = client.UpdateAccountAsync(updatedAccount); var deleteTask = client.DeleteAccountAsync(updatedAccount.Id); // Wait for the two concurrent requests to complete. await Task.WhenAll(updateTask, deleteTask); // Bug: the update request can nondeterministically fail due to an unhandled exception (500 error code). // See the `Update` handler in the account controller for more info. _ = updateTask.Result; var deleteAccountRes = deleteTask.Result; // deleteAccountRes.EnsureSuccessStatusCode(); Assert.IsTrue(deleteAccountRes); }
public async Task TestFirstScenario() { // Initialize the mock in-memory DB and account manager. var cosmosState = new MockCosmosState(); var database = new MockCosmosDatabase(cosmosState); var accountContainer = await database.CreateContainerAsync(Constants.AccountContainerName); var petImagesClient = new TestPetImagesClient(accountContainer); // Create an account request payload var account = new Account() { Name = "MyAccount" }; // Call CreateAccount twice without awaiting, which makes both methods run // asynchronously with each other. var task1 = petImagesClient.CreateAccountAsync(account); var task2 = petImagesClient.CreateAccountAsync(account); // Then wait both requests to complete. await Task.WhenAll(task1, task2); var statusCode1 = task1.Result.StatusCode; var statusCode2 = task2.Result.StatusCode; // Finally, assert that only one of the two requests succeeded and the other // failed. Note that we do not know which one of the two succeeded as the // requests ran concurrently (this is why we use an exclusive OR). Assert.IsTrue( (statusCode1 == HttpStatusCode.OK && statusCode2 == HttpStatusCode.Conflict) || (statusCode1 == HttpStatusCode.Conflict && statusCode2 == HttpStatusCode.OK)); }
public async Task TestThirdScenario() { var cosmosState = new MockCosmosState(); var database = new MockCosmosDatabase(cosmosState); var accountContainer = (MockCosmosContainer)await database.CreateContainerAsync(Constants.AccountContainerName); var imageContainer = (MockCosmosContainer)await database.CreateContainerAsync(Constants.ImageContainerName); var blobContainer = new MockBlobContainerProvider(); var messagingClient = new MockMessagingClient(blobContainer); var petImagesClient = new TestPetImagesClient(accountContainer, imageContainer, blobContainer, messagingClient); string accountName = "MyAccount"; string imageName = "pet.jpg"; // Create an account request payload var account = new Account() { Name = accountName }; var accountResult = await petImagesClient.CreateAccountAsync(account); Assert.IsTrue(accountResult.StatusCode == HttpStatusCode.OK); var task1 = petImagesClient.CreateOrUpdateImageAsync(accountName, new Image() { Name = imageName, Content = GetDogImageBytes() }); var task2 = petImagesClient.CreateOrUpdateImageAsync(accountName, new Image() { Name = imageName, Content = GetCatImageBytes() }); await Task.WhenAll(task1, task2); Assert.IsTrue(task1.Result.StatusCode == HttpStatusCode.OK); Assert.IsTrue(task1.Result.StatusCode == HttpStatusCode.OK); var imageResult = await petImagesClient.GetImageAsync(accountName, imageName); Assert.IsTrue(imageResult.StatusCode == HttpStatusCode.OK); byte[] image = imageResult.Resource; byte[] thumbnail; while (true) { var thumbnailResult = await petImagesClient.GetImageThumbnailAsync(accountName, imageName); if (thumbnailResult.StatusCode == HttpStatusCode.OK) { thumbnail = thumbnailResult.Resource; break; } } Assert.IsTrue( (IsDogImage(image) && IsDogThumbnail(thumbnail)) || (IsCatImage(image) && IsCatThumbnail(thumbnail))); }
public async Task TestSecondScenario() { var cosmosState = new MockCosmosState(); var database = new MockCosmosDatabase(cosmosState); var accountContainer = (MockCosmosContainer)await database.CreateContainerAsync(Constants.AccountContainerName); var imageContainer = (MockCosmosContainer)await database.CreateContainerAsync(Constants.ImageContainerName); var blobContainer = new MockBlobContainerProvider(); var messagingClient = new MockMessagingClient(blobContainer); var petImagesClient = new TestPetImagesClient(accountContainer, imageContainer, blobContainer, messagingClient); string accountName = "MyAccount"; string imageName = "pet.jpg"; // Create an account request payload var account = new Account() { Name = accountName }; var accountResult = await petImagesClient.CreateAccountAsync(account); Assert.IsTrue(accountResult.StatusCode == HttpStatusCode.OK); imageContainer.EnableRandomizedFaults(); var task1 = petImagesClient.CreateImageAsync(accountName, new Image() { Name = imageName, Content = GetDogImageBytes() }); var task2 = petImagesClient.CreateImageAsync(accountName, new Image() { Name = imageName, Content = GetDogImageBytes() }); await Task.WhenAll(task1, task2); var statusCode1 = task1.Result.StatusCode; var statusCode2 = task2.Result.StatusCode; imageContainer.DisableRandomizedFaults(); Assert.IsTrue(statusCode1 == HttpStatusCode.OK || statusCode1 == HttpStatusCode.Conflict || statusCode1 == HttpStatusCode.ServiceUnavailable); Assert.IsTrue(statusCode2 == HttpStatusCode.OK || statusCode2 == HttpStatusCode.Conflict || statusCode2 == HttpStatusCode.ServiceUnavailable); if (task1.Result.StatusCode == HttpStatusCode.OK || task2.Result.StatusCode == HttpStatusCode.OK) { var imageContentResult = await petImagesClient.GetImageAsync(accountName, imageName); Assert.IsTrue(imageContentResult.StatusCode == HttpStatusCode.OK); Assert.IsTrue(IsDogImage(imageContentResult.Resource)); } }
public async Task TestConcurrentAccountAndImageRequestsAsync() { var logger = new MockLogger(); var cosmosState = new MockCosmosState(logger); var client = new MockImageGalleryClient(cosmosState, logger); IDatabaseProvider databaseProvider = await client.InitializeCosmosDbAsync(); // Try create a new account, and wait for it to be created before proceeding with the test. var account = new Account("0", "alice", "*****@*****.**"); await client.CreateAccountAsync(account); // Try store the image and delete the account concurrently, which can cause a data race and a bug. var image = new Image(account.Id, "beach", Encoding.Default.GetBytes("waves")); var storeImageTask = client.CreateOrUpdateImageAsync(image); var deleteAccountTask = client.DeleteAccountAsync(account.Id); // Wait for the two concurrent requests to complete. await Task.WhenAll(storeImageTask, deleteAccountTask); // BUG: The above two concurrent requests can race and result into the image being stored // in an "orphan" container in Azure Storage, even if the associated account was deleted. // Check that the image was deleted from Azure Storage. var exists = await client.AzureStorageProvider.ExistsBlobAsync(Constants.GetContainerName(account.Id), image.Name); if (exists) { throw new AssertFailedException("The image was not deleted from Azure Blob Storage."); } // Check that the account was deleted from Cosmos DB. var accountContainer = databaseProvider.GetContainer(Constants.AccountCollectionName); exists = await accountContainer.ExistsItemAsync <AccountEntity>(account.Id, account.Id); if (exists) { throw new AssertFailedException("The account was not deleted from Cosmos DB."); } }