public async Task ChangeMachineTenantAsync_WhenSuccessfulChange_ReturnsUpdatedMachine() { var machineId = Guid.NewGuid(); _machineRepositoryMock.SetupCreateItemQuery(o => new List <Machine> { new Machine { Id = machineId } }); _tenantApiMock.Setup(m => m.GetTenantByIdAsync(It.IsAny <Guid>())) .ReturnsAsync(MicroserviceResponse.Create(HttpStatusCode.OK, new Tenant())); _cloudShimMock.Setup(m => m.ValidateSettingProfileId(It.IsAny <Guid>(), It.IsAny <Guid>())) .ReturnsAsync(MicroserviceResponse.Create(HttpStatusCode.OK, true)); _machineRepositoryMock.Setup(m => m.UpdateItemAsync(It.IsAny <Guid>(), It.IsAny <Machine>(), It.IsAny <UpdateOptions>(), It.IsAny <CancellationToken>())) .ReturnsAsync((Guid i, Machine m, UpdateOptions o, CancellationToken t) => m); var request = new ChangeMachineTenantRequest { Id = machineId, TenantId = Guid.NewGuid(), SettingProfileId = Guid.NewGuid() }; var result = await _controller.ChangeMachineTenantAsync(request, null, CancellationToken.None); Assert.NotNull(result); }
public async Task ChangeMachineTenantAsync_WhenInvalidSettingProfileId_ThrowsValidationFailedException() { var machineId = Guid.NewGuid(); _machineRepositoryMock.SetupCreateItemQuery(o => new List <Machine> { new Machine { Id = machineId } }); _tenantApiMock.Setup(m => m.GetTenantByIdAsync(It.IsAny <Guid>())) .ReturnsAsync((Guid tenantId) => MicroserviceResponse.Create(HttpStatusCode.OK, new Tenant { Id = tenantId })); _cloudShimMock.Setup(m => m.ValidateSettingProfileId(It.IsAny <Guid>(), It.IsAny <Guid>())) .ReturnsAsync(MicroserviceResponse.Create(HttpStatusCode.OK, false)); var request = new ChangeMachineTenantRequest { Id = machineId, TenantId = Guid.NewGuid(), SettingProfileId = Guid.NewGuid() }; var ex = await Assert.ThrowsAsync <ValidationFailedException>(() => _controller.ChangeMachineTenantAsync(request, null)); Assert.Collection(ex.Errors, failure => Assert.Equal(nameof(ChangeMachineTenantRequest.SettingProfileId), failure.PropertyName)); }
public async Task ChangeMachineTenantAsync_WhenMachineNotFound_ThrowsNotFoundException() { _machineRepositoryMock.SetupCreateItemQuery(); var request = new ChangeMachineTenantRequest { Id = Guid.NewGuid(), TenantId = Guid.NewGuid(), SettingProfileId = Guid.NewGuid() }; await Assert.ThrowsAsync <NotFoundException>(() => _controller.ChangeMachineTenantAsync(request, null)); }
public async Task ChangeMachineTenantAsync_WhenEmptyTenantId_ThrowsValidationFailedException() { var request = new ChangeMachineTenantRequest { Id = Guid.NewGuid(), TenantId = Guid.Empty, SettingProfileId = Guid.NewGuid() }; var ex = await Assert.ThrowsAsync <ValidationFailedException>(() => _controller.ChangeMachineTenantAsync(request, null)); Assert.Collection(ex.Errors, failure => Assert.Equal(nameof(ChangeMachineTenantRequest.TenantId), failure.PropertyName)); }
public async Task <Machine> ChangeMachineTenantAsync(ChangeMachineTenantRequest request, Guid?sourceTenantId, CancellationToken cancellationToken) { // This operation should only be performed by superadmins. While permissions to this // operation should be gated by policy documents, we need to make sure that if someone // gets this far as a non-superadmin, we have additional checks. // We're also assuming here that the existingMachine parameter has been retrieved from // this controller using GetMachineByIdAsync. // First, let's validate the target tenant ID because this is a free-form text field in // the Admin. if (request.TenantId == Guid.Empty) { throw new ValidationFailedException(new[] { new ValidationFailure(nameof(request.TenantId), "The TenantId must not be empty") }); } var machineRepository = await _machineRepositoryAsyncLazy; var batchOptions = sourceTenantId == null ? new BatchOptions { EnableCrossPartitionQuery = true } : new BatchOptions { PartitionKey = new PartitionKey(sourceTenantId.Value) }; var existingMachine = await machineRepository.CreateItemQuery(batchOptions) .FirstOrDefaultAsync(m => m.Id == request.Id, cancellationToken); if (existingMachine == null) { throw new NotFoundException($"Unable to find machine with identifier '{request.Id}'"); } if (existingMachine.TenantId == request.TenantId) { // Nothing to change. return(existingMachine); } // Validate the target tenant by asking the Tenant service. var tenantResponse = await _tenantApi.GetTenantByIdAsync(request.TenantId); if (!tenantResponse.IsSuccess()) { if (tenantResponse.ResponseCode != HttpStatusCode.NotFound) { _logger.Error($"Failed to query the target tenant with identifier '{request.TenantId}' while attempting to move the machine '{request.Id}' to that tenant: ({tenantResponse.ResponseCode}) {tenantResponse.ErrorResponse?.Message ?? tenantResponse.ReasonPhrase}"); } throw new ValidationFailedException(new[] { new ValidationFailure(nameof(request.TenantId), "The TenantId provided in the request is either invalid or cannot be validated") }); } // If the user didn't specify a setting profile identifier, we will pick the first // valid setting profile from the target tenant. var validateSettingProfileId = true; if (request.SettingProfileId == Guid.Empty) { var settingProfileIdsResponse = await _cloudShim.GetSettingProfileIdsForTenant(request.TenantId); if (!settingProfileIdsResponse.IsSuccess() || !settingProfileIdsResponse.Payload.Any()) { throw new ValidationFailedException(new[] { new ValidationFailure(nameof(request.SettingProfileId), "Unable to find a valid setting profile for the machine in the new tenant") }); } request.SettingProfileId = settingProfileIdsResponse.Payload.First(); // We don't need to validate a setting profile that we just obtained. validateSettingProfileId = false; } if (validateSettingProfileId && !await IsValidSettingProfileAsync(request.TenantId, request.SettingProfileId)) { throw new ValidationFailedException(new[] { new ValidationFailure(nameof(request.SettingProfileId), "The setting profile identifier is invalid or could not be validated") }); } existingMachine.TenantId = request.TenantId; existingMachine.SettingProfileId = request.SettingProfileId; return(await machineRepository.UpdateItemAsync(request.Id, existingMachine, cancellationToken : cancellationToken)); }