diff --git a/backend/api.test/Controllers/RobotModelControllerTests.cs b/backend/api.test/Controllers/RobotModelControllerTests.cs new file mode 100644 index 00000000..49df4c3d --- /dev/null +++ b/backend/api.test/Controllers/RobotModelControllerTests.cs @@ -0,0 +1,158 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Json; +using System.Text.Json; +using System.Threading.Tasks; +using Api.Controllers.Models; +using Api.Database.Models; +using Api.Services; +using Api.Test.Database; +using Microsoft.Extensions.DependencyInjection; +using Testcontainers.PostgreSql; +using Xunit; + +namespace Api.Test.Controllers; + +public class RobotModelControllerTests : IAsyncLifetime +{ + public required DatabaseUtilities DatabaseUtilities; + public required PostgreSqlContainer Container; + public required HttpClient Client; + public required JsonSerializerOptions SerializerOptions; + + public required IRobotModelService RobotModelService; + + public async Task InitializeAsync() + { + (Container, var connectionString, var connection) = + await TestSetupHelpers.ConfigurePostgreSqlDatabase(); + var factory = TestSetupHelpers.ConfigureWebApplicationFactory( + postgreSqlConnectionString: connectionString + ); + var serviceProvider = TestSetupHelpers.ConfigureServiceProvider(factory); + + Client = TestSetupHelpers.ConfigureHttpClient(factory); + SerializerOptions = TestSetupHelpers.ConfigureJsonSerializerOptions(); + + DatabaseUtilities = new DatabaseUtilities( + TestSetupHelpers.ConfigurePostgreSqlContext(connectionString) + ); + + RobotModelService = serviceProvider.GetRequiredService(); + } + + public Task DisposeAsync() => Task.CompletedTask; + + [Fact] + public async Task CheckThatListAllRobotModelsReturnsSuccess() + { + var response = await Client.GetAsync("/robot-models"); + var robotModels = await response.Content.ReadFromJsonAsync>( + SerializerOptions + ); + + // Seven models are added by default to the database + // This number must be changed if new robots are introduced + Assert.Equal(7, robotModels!.Count); + } + + [Fact] + public async Task CheckThatLookupRobotModelByRobotTypeReturnsSuccess() + { + const RobotType RobotType = RobotType.Robot; + + var response = await Client.GetAsync("/robot-models/type/" + RobotType); + var robotModel = await response.Content.ReadFromJsonAsync(SerializerOptions); + + Assert.Equal(RobotType, robotModel!.Type); + } + + [Fact] + public async Task CheckThatLookupRobotModelByIdReturnsSuccess() + { + var robotModel = await RobotModelService.ReadByRobotType(RobotType.Robot); + + var response = await Client.GetAsync("/robot-models/" + robotModel!.Id); + var robotModelFromResponse = await response.Content.ReadFromJsonAsync( + SerializerOptions + ); + + Assert.Equal(robotModel.Id, robotModelFromResponse!.Id); + Assert.Equal(robotModel.Type, robotModelFromResponse!.Type); + } + + [Fact] + public async Task CheckThatCreateRobotModelReturnsSuccess() + { + // Arrange + var modelBefore = await RobotModelService.ReadByRobotType(RobotType.Robot); + _ = await RobotModelService.Delete(modelBefore!.Id); + + var query = new CreateRobotModelQuery { RobotType = RobotType.Robot }; + var content = new StringContent(JsonSerializer.Serialize(query), null, "application/json"); + + // Act + var response = await Client.PostAsync("/robot-models", content); + + // Assert + var modelAfter = await RobotModelService.ReadByRobotType(RobotType.Robot); + + Assert.True(response.IsSuccessStatusCode); + Assert.NotEqual(modelBefore!.Id, modelAfter!.Id); + } + + [Theory] + [InlineData(10, 90, 10, 50, true)] + [InlineData(-1, -10, 10, 50, false)] + [InlineData(110, 1000, 10, 900, false)] + [InlineData(0, 9000, 0, 100, true)] + public async Task CheckThatUpdatingRobotBasedOnIdIsSuccessful( + int batteryWarningThreshold, + int upperPressureWarningThreshold, + int lowerPressureWarningThreshold, + int batteryMissionStartThreshold, + bool valuesShouldHaveBeenChanged + ) + { + // Arrange + var modelBefore = await RobotModelService.ReadByRobotType(RobotType.Robot); + + var query = new UpdateRobotModelQuery + { + BatteryWarningThreshold = batteryWarningThreshold, + UpperPressureWarningThreshold = upperPressureWarningThreshold, + LowerPressureWarningThreshold = lowerPressureWarningThreshold, + BatteryMissionStartThreshold = batteryMissionStartThreshold, + }; + var content = new StringContent(JsonSerializer.Serialize(query), null, "application/json"); + + // Act + _ = await Client.PutAsync("/robot-models/" + modelBefore!.Id, content); + + // Assert + var modelAfter = await RobotModelService.ReadByRobotType(RobotType.Robot); + if (valuesShouldHaveBeenChanged) + { + Assert.Equal(modelAfter!.BatteryWarningThreshold, batteryWarningThreshold); + Assert.Equal(modelAfter!.UpperPressureWarningThreshold, upperPressureWarningThreshold); + Assert.Equal(modelAfter!.LowerPressureWarningThreshold, lowerPressureWarningThreshold); + Assert.Equal(modelAfter!.BatteryMissionStartThreshold, batteryMissionStartThreshold); + } + else + { + Assert.Equal(modelAfter!.BatteryWarningThreshold, modelBefore!.BatteryWarningThreshold); + Assert.Equal( + modelAfter!.UpperPressureWarningThreshold, + modelBefore!.UpperPressureWarningThreshold + ); + Assert.Equal( + modelAfter!.LowerPressureWarningThreshold, + modelBefore!.LowerPressureWarningThreshold + ); + Assert.Equal( + modelAfter!.BatteryMissionStartThreshold, + modelBefore!.BatteryMissionStartThreshold + ); + } + } +} diff --git a/backend/api/Controllers/Models/UpdateRobotModelQuery.cs b/backend/api/Controllers/Models/UpdateRobotModelQuery.cs index ef2ce7ee..cfa51168 100644 --- a/backend/api/Controllers/Models/UpdateRobotModelQuery.cs +++ b/backend/api/Controllers/Models/UpdateRobotModelQuery.cs @@ -1,25 +1,31 @@ -namespace Api.Controllers.Models +using System.ComponentModel.DataAnnotations; + +namespace Api.Controllers.Models { public struct UpdateRobotModelQuery { /// /// Lower battery warning threshold in percentage /// + [Range(0, 100, ErrorMessage = "Value must be between 0 and 100")] public float? BatteryWarningThreshold { get; set; } /// /// Upper pressure warning threshold in Bar /// + [Range(0, float.MaxValue, ErrorMessage = "Value must be a non-negative number")] public float? UpperPressureWarningThreshold { get; set; } /// /// Lower pressure warning threshold in Bar /// + [Range(0, float.MaxValue, ErrorMessage = "Value must be a non-negative number")] public float? LowerPressureWarningThreshold { get; set; } /// /// Lower battery threshold at which to allow missions to be scheduled, in percentage /// + [Range(0, 100, ErrorMessage = "Value must be between 0 and 100")] public float? BatteryMissionStartThreshold { get; set; } } } diff --git a/backend/api/Controllers/RobotModelController.cs b/backend/api/Controllers/RobotModelController.cs index 86a51912..67b6e3f7 100644 --- a/backend/api/Controllers/RobotModelController.cs +++ b/backend/api/Controllers/RobotModelController.cs @@ -188,25 +188,6 @@ [FromBody] UpdateRobotModelQuery robotModelQuery return await UpdateModel(robotModel, robotModelQuery); } - /// - /// Deletes the robot model with the specified id from the database. - /// - [HttpDelete] - [Authorize(Roles = Role.Admin)] - [Route("{id}")] - [ProducesResponseType(typeof(RobotModel), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task> DeleteRobotModel([FromRoute] string id) - { - var robotModel = await robotModelService.Delete(id); - if (robotModel is null) - return NotFound($"Area with id {id} not found"); - return Ok(robotModel); - } - private async Task> UpdateModel( RobotModel robotModel, UpdateRobotModelQuery robotModelQuery