// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.IotHub;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.StorageAdapter;
using Moq;
using Newtonsoft.Json;
using Services.Test.helpers;
using Xunit;
using Xunit.Abstractions;
using SimulationModel = Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models.Simulation;
namespace Services.Test
{
public class SimulationsTest
{
/// The test logger
private readonly ITestOutputHelper log;
private const string STORAGE_COLLECTION = "simulations";
private const string SIMULATION_ID = "1";
private readonly Mock deviceModels;
private readonly Mock storage;
private readonly Mock devices;
private readonly Mock logger;
private readonly Mock connStringManager;
private readonly Simulations target;
private readonly List models;
public SimulationsTest(ITestOutputHelper log)
{
this.log = log;
this.deviceModels = new Mock();
this.storage = new Mock();
this.logger = new Mock();
this.devices = new Mock();
this.connStringManager = new Mock();
this.models = new List
{
new DeviceModel { Id = "01" },
new DeviceModel { Id = "05" },
new DeviceModel { Id = "02" },
new DeviceModel { Id = "AA" }
};
this.target = new Simulations(this.deviceModels.Object, this.storage.Object, this.connStringManager.Object, this.devices.Object, this.logger.Object);
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void InitialListIsEmpty()
{
// Arrange
this.ThereAreNoSimulationsInTheStorage();
// Act
var list = this.target.GetListAsync().Result;
// Assert
Assert.Equal(0, list.Count);
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void InitialMetadataAfterCreation()
{
// Arrange
this.ThereAreNoSimulationsInTheStorage();
this.ThereAreSomeDeviceModels();
// Act
SimulationModel result = this.target.InsertAsync(new SimulationModel(), "default").Result;
// Assert
Assert.Equal(1, result.Version);
Assert.Equal(result.Created, result.Modified);
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void CreateDefaultSimulation()
{
// Arrange
const int DEFAULT_DEVICE_COUNT = 1;
this.ThereAreNoSimulationsInTheStorage();
this.ThereAreSomeDeviceModels();
// Act
SimulationModel result = this.target.InsertAsync(new SimulationModel(), "default").Result;
// Assert
Assert.Equal(this.models.Count, result.DeviceModels.Count);
for (var i = 0; i < this.models.Count; i++)
{
Assert.Equal(this.models[i].Id, result.DeviceModels[i].Id);
Assert.Equal(DEFAULT_DEVICE_COUNT, result.DeviceModels[i].Count);
}
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void CreateSimulationWithoutId()
{
// Arrange
this.ThereAreNoSimulationsInTheStorage();
this.ThereAreSomeDeviceModels();
// Act
SimulationModel result = this.target.InsertAsync(new SimulationModel(), "default").Result;
// Assert
Assert.NotEmpty(result.Id);
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void CreateSimulationWithId()
{
// Arrange
this.ThereAreSomeDeviceModels();
this.ThereAreNoSimulationsInTheStorage();
// Act
var simulation = new SimulationModel { Id = "123" };
SimulationModel result = this.target.InsertAsync(simulation, "default").Result;
// Assert
Assert.Equal(simulation.Id, result.Id);
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void CreateWithInvalidTemplate()
{
// Act + Assert
Assert.ThrowsAsync(
() => this.target.InsertAsync(new SimulationModel(), "mytemplate"));
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void CreatingMultipleSimulationsIsNotAllowed()
{
// Arrange
this.ThereAreSomeDeviceModels();
this.ThereIsAnEnabledSimulationInTheStorage();
// Act + Assert
var s = new SimulationModel { Id = Guid.NewGuid().ToString(), Enabled = false };
Assert.ThrowsAsync(() => this.target.InsertAsync(s));
Assert.ThrowsAsync(() => this.target.UpsertAsync(s));
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void CreatedSimulationsAreStored()
{
// Arrange
this.ThereAreSomeDeviceModels();
this.ThereAreNoSimulationsInTheStorage();
// Act
var simulation = new SimulationModel { Id = Guid.NewGuid().ToString(), Enabled = false };
this.target.InsertAsync(simulation, "default").Wait();
// Assert
this.storage.Verify(
x => x.UpdateAsync(STORAGE_COLLECTION, SIMULATION_ID, It.IsAny(), "*"));
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void SimulationsCanBeUpserted()
{
// Arrange
this.ThereAreSomeDeviceModels();
this.ThereAreNoSimulationsInTheStorage();
// Arrange the simulation data returned by the storage adapter
var updatedValue = new ValueApiModel { ETag = "newETag" };
this.storage.Setup(x => x.UpdateAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
.ReturnsAsync(updatedValue);
// Act
var simulation = new SimulationModel
{
Id = SIMULATION_ID,
Enabled = false,
ETag = "oldETag"
};
this.target.UpsertAsync(simulation).Wait();
// Assert
this.storage.Verify(
x => x.UpdateAsync(STORAGE_COLLECTION, SIMULATION_ID, It.IsAny(), "oldETag"));
Assert.Equal("newETag", simulation.ETag);
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void UpsertRequiresIdWhileInsertDoesNot()
{
// Act
var s1 = new SimulationModel();
var s2 = new SimulationModel();
this.target.InsertAsync(s1);
// Act + Assert
Assert.ThrowsAsync(() => this.target.UpsertAsync(s2));
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void UpsertUsesOptimisticConcurrency()
{
// Arrange
this.deviceModels.Setup(x => x.GetList()).Returns(this.models);
var id = Guid.NewGuid().ToString();
var s1 = new SimulationModel { Id = id, Enabled = false };
this.target.UpsertAsync(s1);
// Act + Assert
var s1Updated = new SimulationModel { Id = id, Enabled = true };
Assert.ThrowsAsync(() => this.target.UpsertAsync(s1Updated));
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void ThereAreNoNullPropertiesInTheDeviceModel()
{
// Arrange
this.ThereAreSomeDeviceModels();
this.ThereAreNoSimulationsInTheStorage();
// Arrange the simulation data returned by the storage adapter
var simulation = new SimulationModel
{
Id = SIMULATION_ID,
ETag = "ETag0",
Enabled = true,
Version = 1
};
var updatedValue = new ValueApiModel
{
Key = SIMULATION_ID,
Data = JsonConvert.SerializeObject(simulation),
ETag = simulation.ETag
};
this.storage.Setup(x => x.UpdateAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
.ReturnsAsync(updatedValue);
// Act
this.target.UpsertAsync(simulation).Wait();
// Assert
this.storage.Verify(x => x.UpdateAsync(
STORAGE_COLLECTION,
SIMULATION_ID,
It.Is(s => !s.Contains("null")),
"ETag0"));
}
private void ThereAreSomeDeviceModels()
{
this.deviceModels.Setup(x => x.GetList()).Returns(this.models);
}
private void ThereAreNoSimulationsInTheStorage()
{
this.storage.Setup(x => x.GetAllAsync(STORAGE_COLLECTION)).ReturnsAsync(new ValueListApiModel());
// In case the test inserts a record, return a valid storage object
this.storage.Setup(x => x.UpdateAsync(STORAGE_COLLECTION, SIMULATION_ID, It.IsAny(), "*"))
.ReturnsAsync(new ValueApiModel { Key = SIMULATION_ID, Data = "{}", ETag = "someETag" });
}
private void ThereIsAnEnabledSimulationInTheStorage()
{
var simulation = new SimulationModel
{
Id = SIMULATION_ID,
Created = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(10)),
Modified = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(10)),
ETag = "ETag0",
Enabled = true,
Version = 1
};
var list = new ValueListApiModel();
var value = new ValueApiModel
{
Key = SIMULATION_ID,
Data = JsonConvert.SerializeObject(simulation),
ETag = simulation.ETag
};
list.Items.Add(value);
this.storage.Setup(x => x.GetAllAsync(STORAGE_COLLECTION)).ReturnsAsync(list);
}
}
}