From 40fcbe9fd7b999594bc9001e39aa65297c324701 Mon Sep 17 00:00:00 2001 From: Mauro Gioberti Date: Sun, 18 Aug 2024 17:17:45 +0200 Subject: [PATCH 01/10] Update Api Endpoints Http Request --- .../Poc.TextProcessor.Presentation.RestApi.http | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Poc.TextProcessor.Presentation.RestApi/Poc.TextProcessor.Presentation.RestApi.http b/Poc.TextProcessor.Presentation.RestApi/Poc.TextProcessor.Presentation.RestApi.http index 7b4e77e..4386e78 100644 --- a/Poc.TextProcessor.Presentation.RestApi/Poc.TextProcessor.Presentation.RestApi.http +++ b/Poc.TextProcessor.Presentation.RestApi/Poc.TextProcessor.Presentation.RestApi.http @@ -1,6 +1,13 @@ @Poc.TextProcessor.Presentation.RestApi_HostAddress = http://localhost:5162 -GET {{Poc.TextProcessor.Presentation.RestApi_HostAddress}}/weatherforecast/ +### Retrieve available text processing options +GET {{Poc.TextProcessor.Presentation.RestApi_HostAddress}}/Text/Options Accept: application/json -### +### Get statistics of the provided text +GET {{Poc.TextProcessor.Presentation.RestApi_HostAddress}}/Text/Statistics?textToAnalyze=sampleText +Accept: application/json + +### Order the provided text based on the specified order option +GET {{Poc.TextProcessor.Presentation.RestApi_HostAddress}}/Text/OrderedText?textToOrder=sampleText&orderOption=AlphabeticDesc +Accept: application/json \ No newline at end of file From aa820f02a32171e8b8d914597b27d0b2f4a2315e Mon Sep 17 00:00:00 2001 From: Mauro Gioberti Date: Sun, 18 Aug 2024 17:19:35 +0200 Subject: [PATCH 02/10] Add Docker-based Integrity Assurance setup and related improvements --- .../IntegrityAssuranceInitializer.cs | 87 +++++++++++++++++++ .../Scripts/System/00_DropDatabase.sql | 6 ++ ....TextProcessor.Presentation.RestApi.csproj | 5 ++ .../Program.cs | 3 + .../Properties/launchSettings.json | 11 +++ .../appsettings.json | 5 +- .../docker-compose.yml | 13 +++ 7 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 Poc.TextProcessor.Presentation.RestApi/IntegrityAssurance/IntegrityAssuranceInitializer.cs create mode 100644 Poc.TextProcessor.Presentation.RestApi/IntegrityAssurance/Scripts/System/00_DropDatabase.sql create mode 100644 Poc.TextProcessor.Presentation.RestApi/docker-compose.yml diff --git a/Poc.TextProcessor.Presentation.RestApi/IntegrityAssurance/IntegrityAssuranceInitializer.cs b/Poc.TextProcessor.Presentation.RestApi/IntegrityAssurance/IntegrityAssuranceInitializer.cs new file mode 100644 index 0000000..3094940 --- /dev/null +++ b/Poc.TextProcessor.Presentation.RestApi/IntegrityAssurance/IntegrityAssuranceInitializer.cs @@ -0,0 +1,87 @@ +using Microsoft.EntityFrameworkCore; +using Poc.TextProcessor.ResourceAccess.Database.Providers.EntityFramework; +using System.Diagnostics; + +namespace Poc.TextProcessor.Presentation.RestApi.IntegrityAssurance +{ + public static class IntegrityAssuranceInitializer + { + private const string EnvironmentVariableExecutionMode = "ExecutionMode"; + private const string ExecutionModeIntegrityAssurance = "IntegrityAssurance"; + private const string DockerComposeCommand = "docker-compose"; + private const string DockerComposeUpArguments = "up -d"; + private const string IntegrityAssuranceBaseDirectory = "IntegrityAssurance"; + private const string ScriptsDirectory = "Scripts"; + private const string SystemScriptsDirectory = "System"; + private const string ScriptsExtensions = "*.sql"; + + /// + /// Initializes the application to run in Integrity Assurance mode. + /// This method starts a Docker container, waits for SQL Server to be ready, + /// executes a SQL script to drop the database, and applies any pending migrations. + /// + /// The WebApplication instance to configure. + public static void InitializeIntegrityAssurance(WebApplication app) + { + if (Environment.GetEnvironmentVariable(EnvironmentVariableExecutionMode) == ExecutionModeIntegrityAssurance) + { + Console.WriteLine("Running in Integrity Assurance mode..."); + + var dockerUp = Process.Start(DockerComposeCommand, DockerComposeUpArguments); + dockerUp.WaitForExit(); + + // TODO: Temporary workaround to wait for SQL Server to be ready + Thread.Sleep(30000); + + var systemScriptsDirectory = Path.Combine(Directory.GetCurrentDirectory(), IntegrityAssuranceBaseDirectory, ScriptsDirectory, SystemScriptsDirectory); + ExecuteScriptsDirectory(systemScriptsDirectory); + + // TODO: Migrations will not apply in NHibernate replace this with the provider interface + using (var scope = app.Services.CreateScope()) + { + var dbContext = scope.ServiceProvider.GetRequiredService(); + dbContext.Database.Migrate(); + } + + var testScriptsDirectory = Path.Combine(Directory.GetCurrentDirectory(), IntegrityAssuranceBaseDirectory, ScriptsDirectory); + ExecuteScriptsDirectory(testScriptsDirectory); + } + } + + private static void ExecuteScriptsDirectory(string scriptsDirectory) + { + var scriptFiles = Directory.GetFiles(scriptsDirectory, ScriptsExtensions, SearchOption.AllDirectories) + .Where(f => !f.Contains(SystemScriptsDirectory)) + .OrderBy(f => f); + + foreach (var scriptFile in scriptFiles) + { + try + { + ExecuteSqlScript(scriptFile); + } + catch (Exception ex) + { + //TODO: Add a custom exception and stop the execution + Console.WriteLine($"Error while loading script: {scriptFile}. Exception: {ex.Message}"); + } + } + } + + private static void ExecuteSqlScript(string scriptPath) + { + var sqlCmdProcess = Process.Start(new ProcessStartInfo + { + FileName = "sqlcmd", + Arguments = $"-S localhost -U sa -P YourStrong!Passw0rd -i \"{scriptPath}\"", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }); + + sqlCmdProcess.WaitForExit(); + Console.WriteLine($"SQL script {scriptPath} executed with exit code: {sqlCmdProcess.ExitCode}"); + } + } +} \ No newline at end of file diff --git a/Poc.TextProcessor.Presentation.RestApi/IntegrityAssurance/Scripts/System/00_DropDatabase.sql b/Poc.TextProcessor.Presentation.RestApi/IntegrityAssurance/Scripts/System/00_DropDatabase.sql new file mode 100644 index 0000000..52a1d5e --- /dev/null +++ b/Poc.TextProcessor.Presentation.RestApi/IntegrityAssurance/Scripts/System/00_DropDatabase.sql @@ -0,0 +1,6 @@ +USE master; +IF EXISTS(SELECT * FROM sys.databases WHERE name = 'PocTextProcessor') +BEGIN + ALTER DATABASE [PocTextProcessor] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; + DROP DATABASE [PocTextProcessor]; +END; \ No newline at end of file diff --git a/Poc.TextProcessor.Presentation.RestApi/Poc.TextProcessor.Presentation.RestApi.csproj b/Poc.TextProcessor.Presentation.RestApi/Poc.TextProcessor.Presentation.RestApi.csproj index 1f58d70..4441db1 100644 --- a/Poc.TextProcessor.Presentation.RestApi/Poc.TextProcessor.Presentation.RestApi.csproj +++ b/Poc.TextProcessor.Presentation.RestApi/Poc.TextProcessor.Presentation.RestApi.csproj @@ -7,6 +7,11 @@ false + + + + + diff --git a/Poc.TextProcessor.Presentation.RestApi/Program.cs b/Poc.TextProcessor.Presentation.RestApi/Program.cs index 6653d4a..1c02572 100644 --- a/Poc.TextProcessor.Presentation.RestApi/Program.cs +++ b/Poc.TextProcessor.Presentation.RestApi/Program.cs @@ -2,6 +2,7 @@ using Poc.TextProcessor.Business.Logic.Abstractions; using Poc.TextProcessor.CrossCutting.Logging; using Poc.TextProcessor.Presentation.RestApi.Infrastructure.FilterAttributes; +using Poc.TextProcessor.Presentation.RestApi.IntegrityAssurance; using Poc.TextProcessor.ResourceAccess.Database.Providers.Configuration; using Poc.TextProcessor.ResourceAccess.Mappers; using Poc.TextProcessor.ResourceAccess.Repositories; @@ -42,6 +43,8 @@ var app = builder.Build(); +IntegrityAssuranceInitializer.InitializeIntegrityAssurance(app); + // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { diff --git a/Poc.TextProcessor.Presentation.RestApi/Properties/launchSettings.json b/Poc.TextProcessor.Presentation.RestApi/Properties/launchSettings.json index 2ba69b8..6e52f21 100644 --- a/Poc.TextProcessor.Presentation.RestApi/Properties/launchSettings.json +++ b/Poc.TextProcessor.Presentation.RestApi/Properties/launchSettings.json @@ -36,6 +36,17 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } + }, + "IntegrityAssurance": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:6001;http://localhost:6000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "ExecutionMode": "IntegrityAssurance" + } } } } diff --git a/Poc.TextProcessor.Presentation.RestApi/appsettings.json b/Poc.TextProcessor.Presentation.RestApi/appsettings.json index ba87be7..8b54f87 100644 --- a/Poc.TextProcessor.Presentation.RestApi/appsettings.json +++ b/Poc.TextProcessor.Presentation.RestApi/appsettings.json @@ -7,9 +7,10 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "SqlServerConnection": "Server=localhost;Database=PocTextProcessor;Trusted_Connection=True;TrustServerCertificate=True", + //"SqlServerConnection": "Server=localhost;Database=PocTextProcessor;Trusted_Connection=True;TrustServerCertificate=True", + "SqlServerConnection": "Server=localhost;Database=PocTextProcessor;User Id=sa;Password=YourStrong!Passw0rd;TrustServerCertificate=True", "SqliteConnection": "Data Source=..\\Poc.TextProcessor.ResourceAccess.Database\\PocTextProcessor.db;Cache=Shared" }, "DatabaseProvider": "SqlServer", // Specify the database provider: "Sqlite" or "SqlServer" - "DatabaseFramework": "NHibernate" // Specify the database framework: "NHibernate" or "EntityFramework" + "DatabaseFramework": "EntityFramework" // Specify the database framework: "NHibernate" or "EntityFramework" } \ No newline at end of file diff --git a/Poc.TextProcessor.Presentation.RestApi/docker-compose.yml b/Poc.TextProcessor.Presentation.RestApi/docker-compose.yml new file mode 100644 index 0000000..be59128 --- /dev/null +++ b/Poc.TextProcessor.Presentation.RestApi/docker-compose.yml @@ -0,0 +1,13 @@ +services: + sqlserver: + image: mcr.microsoft.com/mssql/server:2019-latest + container_name: sqlserver_test + environment: + SA_PASSWORD: "YourStrong!Passw0rd" + ACCEPT_EULA: "Y" + ports: + - "1433:1433" + healthcheck: + test: ["CMD-SHELL", "sqlcmd -U sa -P YourStrong!Passw0rd -Q 'SELECT 1'"] + interval: 10s + retries: 10 \ No newline at end of file From 68079d67cd637dc7a22fc376db09dc075110d181 Mon Sep 17 00:00:00 2001 From: Mauro Gioberti Date: Sun, 18 Aug 2024 20:19:59 +0200 Subject: [PATCH 03/10] IntegrityAssurance Scripts Insert Order Fix --- .../IntegrityAssurance/IntegrityAssuranceInitializer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Poc.TextProcessor.Presentation.RestApi/IntegrityAssurance/IntegrityAssuranceInitializer.cs b/Poc.TextProcessor.Presentation.RestApi/IntegrityAssurance/IntegrityAssuranceInitializer.cs index 3094940..fc40c8d 100644 --- a/Poc.TextProcessor.Presentation.RestApi/IntegrityAssurance/IntegrityAssuranceInitializer.cs +++ b/Poc.TextProcessor.Presentation.RestApi/IntegrityAssurance/IntegrityAssuranceInitializer.cs @@ -50,9 +50,9 @@ public static void InitializeIntegrityAssurance(WebApplication app) private static void ExecuteScriptsDirectory(string scriptsDirectory) { - var scriptFiles = Directory.GetFiles(scriptsDirectory, ScriptsExtensions, SearchOption.AllDirectories) - .Where(f => !f.Contains(SystemScriptsDirectory)) - .OrderBy(f => f); + var scriptFiles = Directory + .GetFiles(scriptsDirectory, ScriptsExtensions, SearchOption.TopDirectoryOnly) + .OrderBy(f => f); foreach (var scriptFile in scriptFiles) { From b75dd5543b9cd25c1e1e7143f6fde9c267cf5300 Mon Sep 17 00:00:00 2001 From: Mauro Gioberti Date: Sun, 18 Aug 2024 21:04:38 +0200 Subject: [PATCH 04/10] Add Get Endpoints and Integrity Assurance Tests --- .../ITextLogic.cs | 2 + Poc.TextProcessor.Business.Logic/TextLogic.cs | 7 ++ .../Contracts/Collections/TextCollection.cs | 4 + .../Settings/Endpoints.cs | 11 ++- .../Endpoints/Text/GetTests.cs | 73 +++++++++++++++++++ .../Helpers/ValidationHelper.cs | 10 +++ .../appsettings.json | 2 +- .../Controllers/TextController.cs | 18 +++++ .../Scripts/01-AddTextValue.sql | 7 ++ .../Collections/TextCollection.cs | 4 + .../ITextMapper.cs | 5 +- .../TextSortMapper.cs | 10 ++- .../ITextRepository.cs | 1 + .../TextRepository.cs | 6 ++ .../ITextService.cs | 3 + Poc.TextProcessor.Services/TextService.cs | 29 ++++++++ 16 files changed, 186 insertions(+), 6 deletions(-) create mode 100644 Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Core/Contracts/Collections/TextCollection.cs create mode 100644 Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/Endpoints/Text/GetTests.cs create mode 100644 Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/Helpers/ValidationHelper.cs create mode 100644 Poc.TextProcessor.Presentation.RestApi/IntegrityAssurance/Scripts/01-AddTextValue.sql create mode 100644 Poc.TextProcessor.ResourceAccess.Contracts/Collections/TextCollection.cs diff --git a/Poc.TextProcessor.Business.Logic.Abstractions/ITextLogic.cs b/Poc.TextProcessor.Business.Logic.Abstractions/ITextLogic.cs index b047cd1..6e46832 100644 --- a/Poc.TextProcessor.Business.Logic.Abstractions/ITextLogic.cs +++ b/Poc.TextProcessor.Business.Logic.Abstractions/ITextLogic.cs @@ -1,9 +1,11 @@ using Poc.TextProcessor.ResourceAccess.Contracts; +using Poc.TextProcessor.ResourceAccess.Contracts.Collections; namespace Poc.TextProcessor.Business.Logic.Abstractions { public interface ITextLogic { + TextCollection Get(); Text Get(int id); Text GetRandom(); Statistics GetStatistics(string textContent); diff --git a/Poc.TextProcessor.Business.Logic/TextLogic.cs b/Poc.TextProcessor.Business.Logic/TextLogic.cs index bed98d0..b448209 100644 --- a/Poc.TextProcessor.Business.Logic/TextLogic.cs +++ b/Poc.TextProcessor.Business.Logic/TextLogic.cs @@ -3,6 +3,7 @@ using Poc.TextProcessor.Business.Logic.Base; using Poc.TextProcessor.CrossCutting.Utils.Constants; using Poc.TextProcessor.ResourceAccess.Contracts; +using Poc.TextProcessor.ResourceAccess.Contracts.Collections; using Poc.TextProcessor.ResourceAccess.Mappers; using Poc.TextProcessor.ResourceAccess.Repositories.Abstractions; @@ -20,6 +21,12 @@ public Text Get(int id) return _textMapper.Map(textDomain); } + public TextCollection Get() + { + var textDomains = _textRepository.Get(); + return _textMapper.MapCollection(textDomains); + } + public Text GetRandom() { var random = new Random(); diff --git a/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Core/Contracts/Collections/TextCollection.cs b/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Core/Contracts/Collections/TextCollection.cs new file mode 100644 index 0000000..7816111 --- /dev/null +++ b/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Core/Contracts/Collections/TextCollection.cs @@ -0,0 +1,4 @@ +namespace Poc.TextProcessor.IntegrityAssurance.Core.Contracts.Collections +{ + public record TextCollection(IEnumerable Items, int TotalCount); +} diff --git a/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Core/Settings/Endpoints.cs b/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Core/Settings/Endpoints.cs index 3b89863..2862fab 100644 --- a/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Core/Settings/Endpoints.cs +++ b/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Core/Settings/Endpoints.cs @@ -6,18 +6,23 @@ public static class Text { private static string BaseEndpoint => "Text"; - #region Options Endpoint + #region Options Endpoints public static string OptionsEndpoint => $"{BaseEndpoint}/Options"; public static string OptionsInvalidEndpoint => $"{BaseEndpoint}/Options/InvalidRequesttt"; #endregion - #region Ordered Text Endpoint + #region Ordered Text Endpoints public static string OrderedTextEndpoint(string text, string orderOption) => $"{BaseEndpoint}/OrderedText?textToOrder={text}&orderOption={orderOption}"; public static string OrderedTextInvalidEndpoint => $"{BaseEndpoint}/OrderedText/1/2"; public static string OrderedTextBadRequestEndpoint => $"{BaseEndpoint}/OrderedText?textToOrder=&orderOption="; #endregion - #region Statitics Endpoint + #region Get Text Endpoints + public static string Get(int id) => $"{BaseEndpoint}/Get?id={id}"; + public static string GetAll => $"{BaseEndpoint}/GetAll"; + #endregion + + #region Statitics Endpoints public static string StatisticsEndpoint(string text) => $"{BaseEndpoint}/Statistics?textToAnalyze={text}"; public static string StatisticsInvalidEndpoint => $"{BaseEndpoint}/Statistics////"; public static string StatisticsNoParametersEndpoint => $"{BaseEndpoint}/Statistics"; diff --git a/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/Endpoints/Text/GetTests.cs b/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/Endpoints/Text/GetTests.cs new file mode 100644 index 0000000..39f2f3f --- /dev/null +++ b/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/Endpoints/Text/GetTests.cs @@ -0,0 +1,73 @@ +using Poc.TextProcessor.IntegrityAssurance.Core.Contracts.Collections; +using Poc.TextProcessor.IntegrityAssurance.Core.Settings; +using Poc.TextProcessor.IntegrityAssurance.Tests.Endpoints.Base; +using Poc.TextProcessor.IntegrityAssurance.Tests.Helpers; +using System.Net; + +namespace Poc.TextProcessor.IntegrityAssurance.Tests.Endpoints.Text +{ + public class GetTests : TestsBase + { + private const int ExpectedGetId = 10002; + + [Test] + public async Task GetAll_When_Called_Should_Return_Ok() + { + var request = new RestRequest(Core.Settings.Endpoints.Text.GetAll, Method.Get); + var response = await _client.ExecuteAsync(request); + + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); + Assert.That(response.ContentType, Is.EqualTo(Headers.ContentType.ApplicationJson)); + } + + [Test] + public async Task GetAll_When_Called_Should_Return_Inserted_Text() + { + var request = new RestRequest(Core.Settings.Endpoints.Text.GetAll, Method.Get); + var response = await _client.ExecuteAsync(request); + var textCollectionResponse = response.Data; + + var expectedTextContent = "Test Expected Result."; + var expectedTextId = 10001; + + + Assert.That(response.IsSuccessful); + + Assert.That(textCollectionResponse.Items.Any()); + + var textItem = textCollectionResponse.Items.Single(x => x.Id == expectedTextId); + + ValidationHelper.AssertIntegrityScriptNotNull(textItem, expectedTextId); + + //Compare Expected Result + Assert.That(textItem.Content, Is.EqualTo(expectedTextContent)); + } + + + [Test] + public async Task Get_When_Called_Should_Return_Ok() + { + var request = new RestRequest($"{Core.Settings.Endpoints.Text.Get(ExpectedGetId)}", Method.Get); + var response = await _client.ExecuteAsync(request); + + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); + Assert.That(response.ContentType, Is.EqualTo(Headers.ContentType.ApplicationJson)); + } + + [Test] + public async Task Get_When_Called_Should_Return_Inserted_Text() + { + var expectedTextContent = "Test Result for Get by Id."; + + var request = new RestRequest($"{Core.Settings.Endpoints.Text.Get(ExpectedGetId)}", Method.Get); + var response = await _client.ExecuteAsync(request); + var textItem = response.Data; + + Assert.That(response.IsSuccessful); + + ValidationHelper.AssertIntegrityScriptNotNull(textItem, ExpectedGetId); + + Assert.That(textItem.Content, Is.EqualTo(expectedTextContent)); + } + } +} \ No newline at end of file diff --git a/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/Helpers/ValidationHelper.cs b/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/Helpers/ValidationHelper.cs new file mode 100644 index 0000000..1995a17 --- /dev/null +++ b/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/Helpers/ValidationHelper.cs @@ -0,0 +1,10 @@ +namespace Poc.TextProcessor.IntegrityAssurance.Tests.Helpers +{ + public static class ValidationHelper + { + public static void AssertIntegrityScriptNotNull(T textItem, int expectedId) where T : class + { + Assert.That(textItem, Is.Not.Null, $"Integrity Assurance Test Script with Id {expectedId} could not be found."); + } + } +} \ No newline at end of file diff --git a/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/appsettings.json b/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/appsettings.json index e7140ec..d7f153f 100644 --- a/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/appsettings.json +++ b/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/appsettings.json @@ -1,3 +1,3 @@ { - "BaseURL": "http://localhost:5162/" + "BaseURL": "https://localhost:6001/" } \ No newline at end of file diff --git a/Poc.TextProcessor.Presentation.RestApi/Controllers/TextController.cs b/Poc.TextProcessor.Presentation.RestApi/Controllers/TextController.cs index 24756d9..9670b9f 100644 --- a/Poc.TextProcessor.Presentation.RestApi/Controllers/TextController.cs +++ b/Poc.TextProcessor.Presentation.RestApi/Controllers/TextController.cs @@ -18,6 +18,24 @@ public class TextController(ITextService textService, ITextSortService textSortS private readonly ITextService _textService = textService; private readonly ITextSortService _textSortService = textSortService; + [HttpGet("GetAll")] + [Produces("application/json", "application/xml")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(TextCollection))] + public IActionResult GetAll() + { + var sortOptions = _textService.Get(); + return Ok(sortOptions); + } + + [HttpGet("Get")] + [Produces("application/json", "application/xml")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(TextCollection))] + public IActionResult Get(int id) + { + var sortOptions = _textService.Get(id); + return Ok(sortOptions); + } + [HttpGet("Options")] [Produces("application/json", "application/xml")] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(SortCollection))] diff --git a/Poc.TextProcessor.Presentation.RestApi/IntegrityAssurance/Scripts/01-AddTextValue.sql b/Poc.TextProcessor.Presentation.RestApi/IntegrityAssurance/Scripts/01-AddTextValue.sql new file mode 100644 index 0000000..ae3611b --- /dev/null +++ b/Poc.TextProcessor.Presentation.RestApi/IntegrityAssurance/Scripts/01-AddTextValue.sql @@ -0,0 +1,7 @@ +SET IDENTITY_INSERT [dbo].[Texts] ON; + +INSERT INTO [dbo].[Texts]([Id], [Content]) VALUES (10001, N'Test Expected Result.'); +INSERT INTO [dbo].[Texts]([Id], [Content]) VALUES (10002, N'Test Result for Get by Id.'); +INSERT INTO [dbo].[Texts]([Id], [Content]) VALUES (10003, N'Test Result for Delete.'); + +SET IDENTITY_INSERT [dbo].[Texts] OFF; \ No newline at end of file diff --git a/Poc.TextProcessor.ResourceAccess.Contracts/Collections/TextCollection.cs b/Poc.TextProcessor.ResourceAccess.Contracts/Collections/TextCollection.cs new file mode 100644 index 0000000..9d4279b --- /dev/null +++ b/Poc.TextProcessor.ResourceAccess.Contracts/Collections/TextCollection.cs @@ -0,0 +1,4 @@ +namespace Poc.TextProcessor.ResourceAccess.Contracts.Collections +{ + public record TextCollection(IEnumerable Items, int TotalCount); +} \ No newline at end of file diff --git a/Poc.TextProcessor.ResourceAccess.Mappers.Abstractions/ITextMapper.cs b/Poc.TextProcessor.ResourceAccess.Mappers.Abstractions/ITextMapper.cs index 9dc299f..bba1bd2 100644 --- a/Poc.TextProcessor.ResourceAccess.Mappers.Abstractions/ITextMapper.cs +++ b/Poc.TextProcessor.ResourceAccess.Mappers.Abstractions/ITextMapper.cs @@ -1,7 +1,10 @@ -namespace Poc.TextProcessor.ResourceAccess.Mappers +using Poc.TextProcessor.ResourceAccess.Contracts.Collections; + +namespace Poc.TextProcessor.ResourceAccess.Mappers { public interface ITextMapper { Contracts.Text Map(Domains.Text text); + TextCollection MapCollection(IEnumerable items); } } \ No newline at end of file diff --git a/Poc.TextProcessor.ResourceAccess.Mappers/TextSortMapper.cs b/Poc.TextProcessor.ResourceAccess.Mappers/TextSortMapper.cs index 98f5278..bb139da 100644 --- a/Poc.TextProcessor.ResourceAccess.Mappers/TextSortMapper.cs +++ b/Poc.TextProcessor.ResourceAccess.Mappers/TextSortMapper.cs @@ -1,4 +1,6 @@ -namespace Poc.TextProcessor.ResourceAccess.Mappers +using Poc.TextProcessor.ResourceAccess.Contracts.Collections; + +namespace Poc.TextProcessor.ResourceAccess.Mappers { public class TextMapper : ITextMapper { @@ -7,5 +9,11 @@ public class TextMapper : ITextMapper Id = text.Id, Content = text.Content }; + + public TextCollection MapCollection(IEnumerable items) + { + var contracts = items.Select(Map); + return new TextCollection(contracts, contracts.Count()); + } } } \ No newline at end of file diff --git a/Poc.TextProcessor.ResourceAccess.Repositories.Abstractions/ITextRepository.cs b/Poc.TextProcessor.ResourceAccess.Repositories.Abstractions/ITextRepository.cs index 4f204e5..5ebbd2a 100644 --- a/Poc.TextProcessor.ResourceAccess.Repositories.Abstractions/ITextRepository.cs +++ b/Poc.TextProcessor.ResourceAccess.Repositories.Abstractions/ITextRepository.cs @@ -2,6 +2,7 @@ { public interface ITextRepository { + IEnumerable Get(); Domains.Text Get(int id); } } \ No newline at end of file diff --git a/Poc.TextProcessor.ResourceAccess.Repositories/TextRepository.cs b/Poc.TextProcessor.ResourceAccess.Repositories/TextRepository.cs index faa9bbd..03f144f 100644 --- a/Poc.TextProcessor.ResourceAccess.Repositories/TextRepository.cs +++ b/Poc.TextProcessor.ResourceAccess.Repositories/TextRepository.cs @@ -14,5 +14,11 @@ public Text Get(int id) var text = _databaseProvider.Get(x => x.Id == id).Single(); return AutoMap.Map(text); } + + public IEnumerable Get() + { + var text = _databaseProvider.Get(); + return AutoMap.Map(text); + } } } \ No newline at end of file diff --git a/Poc.TextProcessor.Services.Abstractions/ITextService.cs b/Poc.TextProcessor.Services.Abstractions/ITextService.cs index af89b02..f798e3d 100644 --- a/Poc.TextProcessor.Services.Abstractions/ITextService.cs +++ b/Poc.TextProcessor.Services.Abstractions/ITextService.cs @@ -1,9 +1,12 @@ using Poc.TextProcessor.ResourceAccess.Contracts; +using Poc.TextProcessor.ResourceAccess.Contracts.Collections; namespace Poc.TextProcessor.Services.Abstractions { public interface ITextService { + Text Get(int id); + TextCollection Get(); Text GetRandom(); Statistics GetStatistics(string textContent); } diff --git a/Poc.TextProcessor.Services/TextService.cs b/Poc.TextProcessor.Services/TextService.cs index 5590b48..3caccf7 100644 --- a/Poc.TextProcessor.Services/TextService.cs +++ b/Poc.TextProcessor.Services/TextService.cs @@ -1,5 +1,6 @@ using Poc.TextProcessor.Business.Logic.Abstractions; using Poc.TextProcessor.ResourceAccess.Contracts; +using Poc.TextProcessor.ResourceAccess.Contracts.Collections; using Poc.TextProcessor.Services.Abstractions; using Poc.TextProcessor.Services.Base; @@ -9,6 +10,34 @@ public class TextService(ITextLogic textLogic) : ServiceBase, ITextService { private readonly ITextLogic _textLogic = textLogic; + public Text Get(int id) + { + try + { + return _textLogic.Get(id); + } + catch (Exception e) + { + HandleException(e); + } + + return null; + } + + public TextCollection Get() + { + try + { + return _textLogic.Get(); + } + catch (Exception e) + { + HandleException(e); + } + + return null; + } + public Text GetRandom() { try From 79342e614742818dbb11cc22a8ed1e45505edd14 Mon Sep 17 00:00:00 2001 From: Mauro Gioberti Date: Sun, 18 Aug 2024 22:26:28 +0200 Subject: [PATCH 05/10] Update Database Writter to Handle Delete Statement --- .../Abstractions/IDatabaseWriterProvider.cs | 2 ++ .../EntityFrameworkManagerProvider.cs | 10 +++++++++ .../EntityFrameworkWriterProvider.cs | 14 ++++++++++++- .../NHibernate/NHibernateManagerProvider.cs | 10 +++++++++ .../NHibernate/NHibernateWriterProvider.cs | 21 +++++++++++++++++++ 5 files changed, 56 insertions(+), 1 deletion(-) diff --git a/Poc.TextProcessor.ResourceAccess.Database/Abstractions/IDatabaseWriterProvider.cs b/Poc.TextProcessor.ResourceAccess.Database/Abstractions/IDatabaseWriterProvider.cs index bf22788..4c1e460 100644 --- a/Poc.TextProcessor.ResourceAccess.Database/Abstractions/IDatabaseWriterProvider.cs +++ b/Poc.TextProcessor.ResourceAccess.Database/Abstractions/IDatabaseWriterProvider.cs @@ -4,5 +4,7 @@ public interface IDatabaseWriterProvider { T Save(T entity) where T : class; Task SaveAsync(T entity) where T : class; + void Remove(T entity) where T : class; + Task RemoveAsync(T entity) where T : class; } } \ No newline at end of file diff --git a/Poc.TextProcessor.ResourceAccess.Database/Providers/EntityFramework/EntityFrameworkManagerProvider.cs b/Poc.TextProcessor.ResourceAccess.Database/Providers/EntityFramework/EntityFrameworkManagerProvider.cs index df95046..0aae5f3 100644 --- a/Poc.TextProcessor.ResourceAccess.Database/Providers/EntityFramework/EntityFrameworkManagerProvider.cs +++ b/Poc.TextProcessor.ResourceAccess.Database/Providers/EntityFramework/EntityFrameworkManagerProvider.cs @@ -52,6 +52,16 @@ public async Task SaveAsync(T entity) where T : class { return await _writerProvider.SaveAsync(entity); } + + public void Remove(T entity) where T : class + { + _writerProvider.Remove(entity); + } + + public async Task RemoveAsync(T entity) where T : class + { + await _writerProvider.RemoveAsync(entity); + } #endregion public int Execute(string sql, object parameters = null) diff --git a/Poc.TextProcessor.ResourceAccess.Database/Providers/EntityFramework/EntityFrameworkWriterProvider.cs b/Poc.TextProcessor.ResourceAccess.Database/Providers/EntityFramework/EntityFrameworkWriterProvider.cs index 23b42df..a5582b2 100644 --- a/Poc.TextProcessor.ResourceAccess.Database/Providers/EntityFramework/EntityFrameworkWriterProvider.cs +++ b/Poc.TextProcessor.ResourceAccess.Database/Providers/EntityFramework/EntityFrameworkWriterProvider.cs @@ -21,6 +21,18 @@ public async Task SaveAsync(T entity) where T : class return entity; } + public void Remove(T entity) where T : class + { + _context.Set().Remove(entity); + _context.SaveChanges(); + } + + public async Task RemoveAsync(T entity) where T : class + { + _context.Set().Remove(entity); + await _context.SaveChangesAsync(); + } + private void AddOrUpdate(T entity) where T : class { var entry = _context.Entry(entity); @@ -45,4 +57,4 @@ private async Task AddOrUpdateAsync(T entity) where T : class } } } -} +} \ No newline at end of file diff --git a/Poc.TextProcessor.ResourceAccess.Database/Providers/NHibernate/NHibernateManagerProvider.cs b/Poc.TextProcessor.ResourceAccess.Database/Providers/NHibernate/NHibernateManagerProvider.cs index 0449ab9..708dbb3 100644 --- a/Poc.TextProcessor.ResourceAccess.Database/Providers/NHibernate/NHibernateManagerProvider.cs +++ b/Poc.TextProcessor.ResourceAccess.Database/Providers/NHibernate/NHibernateManagerProvider.cs @@ -53,6 +53,16 @@ public async Task SaveAsync(T entity) where T : class { return await _writerProvider.SaveAsync(entity); } + + public void Remove(T entity) where T : class + { + _writerProvider.Remove(entity); + } + + public async Task RemoveAsync(T entity) where T : class + { + await _writerProvider.RemoveAsync(entity); + } #endregion public int Execute(string sql, object parameters = null) diff --git a/Poc.TextProcessor.ResourceAccess.Database/Providers/NHibernate/NHibernateWriterProvider.cs b/Poc.TextProcessor.ResourceAccess.Database/Providers/NHibernate/NHibernateWriterProvider.cs index a290f52..8a3a593 100644 --- a/Poc.TextProcessor.ResourceAccess.Database/Providers/NHibernate/NHibernateWriterProvider.cs +++ b/Poc.TextProcessor.ResourceAccess.Database/Providers/NHibernate/NHibernateWriterProvider.cs @@ -28,5 +28,26 @@ public async Task SaveAsync(T entity) where T : class return entity; } } + + + public void Remove(T entity) where T : class + { + using (var session = _sessionFactory.OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete(entity); + transaction.Commit(); + } + } + + public async Task RemoveAsync(T entity) where T : class + { + using (var session = _sessionFactory.OpenSession()) + using (var transaction = session.BeginTransaction()) + { + await session.DeleteAsync(entity); + await transaction.CommitAsync(); + } + } } } \ No newline at end of file From dcd535bc404d23559b5ead20243d583424afa9b0 Mon Sep 17 00:00:00 2001 From: Mauro Gioberti Date: Sun, 18 Aug 2024 22:26:55 +0200 Subject: [PATCH 06/10] Add Delete Endpoint --- .../ITextLogic.cs | 1 + Poc.TextProcessor.Business.Logic/TextLogic.cs | 5 +++++ .../Controllers/TextController.cs | 16 +++++++++++++++- .../ITextRepository.cs | 1 + .../TextRepository.cs | 8 +++++++- .../ITextService.cs | 1 + Poc.TextProcessor.Services/TextService.cs | 12 ++++++++++++ 7 files changed, 42 insertions(+), 2 deletions(-) diff --git a/Poc.TextProcessor.Business.Logic.Abstractions/ITextLogic.cs b/Poc.TextProcessor.Business.Logic.Abstractions/ITextLogic.cs index 6e46832..9493d46 100644 --- a/Poc.TextProcessor.Business.Logic.Abstractions/ITextLogic.cs +++ b/Poc.TextProcessor.Business.Logic.Abstractions/ITextLogic.cs @@ -7,6 +7,7 @@ public interface ITextLogic { TextCollection Get(); Text Get(int id); + void Remove(int id); Text GetRandom(); Statistics GetStatistics(string textContent); } diff --git a/Poc.TextProcessor.Business.Logic/TextLogic.cs b/Poc.TextProcessor.Business.Logic/TextLogic.cs index b448209..9e6a329 100644 --- a/Poc.TextProcessor.Business.Logic/TextLogic.cs +++ b/Poc.TextProcessor.Business.Logic/TextLogic.cs @@ -21,6 +21,11 @@ public Text Get(int id) return _textMapper.Map(textDomain); } + public void Remove(int id) + { + _textRepository.Remove(id); + } + public TextCollection Get() { var textDomains = _textRepository.Get(); diff --git a/Poc.TextProcessor.Presentation.RestApi/Controllers/TextController.cs b/Poc.TextProcessor.Presentation.RestApi/Controllers/TextController.cs index 9670b9f..71ba828 100644 --- a/Poc.TextProcessor.Presentation.RestApi/Controllers/TextController.cs +++ b/Poc.TextProcessor.Presentation.RestApi/Controllers/TextController.cs @@ -29,13 +29,27 @@ public IActionResult GetAll() [HttpGet("Get")] [Produces("application/json", "application/xml")] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(TextCollection))] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Text))] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] public IActionResult Get(int id) { var sortOptions = _textService.Get(id); return Ok(sortOptions); } + [HttpDelete("Delete")] + [Produces("application/json", "application/xml")] + [ProducesResponseType(StatusCodes.Status204NoContent, Type = typeof(Text))] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public IActionResult Delete(int id) + { + _textService.Remove(id); + return NoContent(); + } + + [HttpGet("Options")] [Produces("application/json", "application/xml")] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(SortCollection))] diff --git a/Poc.TextProcessor.ResourceAccess.Repositories.Abstractions/ITextRepository.cs b/Poc.TextProcessor.ResourceAccess.Repositories.Abstractions/ITextRepository.cs index 5ebbd2a..fcc2575 100644 --- a/Poc.TextProcessor.ResourceAccess.Repositories.Abstractions/ITextRepository.cs +++ b/Poc.TextProcessor.ResourceAccess.Repositories.Abstractions/ITextRepository.cs @@ -4,5 +4,6 @@ public interface ITextRepository { IEnumerable Get(); Domains.Text Get(int id); + void Remove(int id); } } \ No newline at end of file diff --git a/Poc.TextProcessor.ResourceAccess.Repositories/TextRepository.cs b/Poc.TextProcessor.ResourceAccess.Repositories/TextRepository.cs index 03f144f..452bc9e 100644 --- a/Poc.TextProcessor.ResourceAccess.Repositories/TextRepository.cs +++ b/Poc.TextProcessor.ResourceAccess.Repositories/TextRepository.cs @@ -7,7 +7,7 @@ namespace Poc.TextProcessor.ResourceAccess.Repositories { - public class TextRepository(IDatabaseReaderProvider databaseProvider) : RepositoryReaderBase(databaseProvider), ITextRepository + public class TextRepository(IDatabaseManagerProvider databaseProvider) : RepositoryManagerBase(databaseProvider), ITextRepository { public Text Get(int id) { @@ -15,6 +15,12 @@ public Text Get(int id) return AutoMap.Map(text); } + public void Remove(int id) + { + var text = _databaseProvider.Get(x => x.Id == id).Single(); + _databaseProvider.Remove(text); + } + public IEnumerable Get() { var text = _databaseProvider.Get(); diff --git a/Poc.TextProcessor.Services.Abstractions/ITextService.cs b/Poc.TextProcessor.Services.Abstractions/ITextService.cs index f798e3d..2632621 100644 --- a/Poc.TextProcessor.Services.Abstractions/ITextService.cs +++ b/Poc.TextProcessor.Services.Abstractions/ITextService.cs @@ -6,6 +6,7 @@ namespace Poc.TextProcessor.Services.Abstractions public interface ITextService { Text Get(int id); + void Remove(int id); TextCollection Get(); Text GetRandom(); Statistics GetStatistics(string textContent); diff --git a/Poc.TextProcessor.Services/TextService.cs b/Poc.TextProcessor.Services/TextService.cs index 3caccf7..a8bdf70 100644 --- a/Poc.TextProcessor.Services/TextService.cs +++ b/Poc.TextProcessor.Services/TextService.cs @@ -24,6 +24,18 @@ public Text Get(int id) return null; } + public void Remove(int id) + { + try + { + _textLogic.Remove(id); + } + catch (Exception e) + { + HandleException(e); + } + } + public TextCollection Get() { try From f6fe67bec7280b42a403bf447b38a90df201aa2b Mon Sep 17 00:00:00 2001 From: Mauro Gioberti Date: Mon, 19 Aug 2024 00:13:07 +0200 Subject: [PATCH 07/10] Add Delete Endpoint Happy Path Test --- Poc.TextProcessor.Business.Logic/TextLogic.cs | 6 +++++ .../Helpers/HttpStatusHelper.cs | 13 ++++++++++ .../Settings/Endpoints.cs | 4 +++ .../Endpoints/Text/DeleteTests.cs | 25 +++++++++++++++++++ 4 files changed, 48 insertions(+) create mode 100644 Poc.TextProcessor.CrossCutting.Utils/Helpers/HttpStatusHelper.cs create mode 100644 Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/Endpoints/Text/DeleteTests.cs diff --git a/Poc.TextProcessor.Business.Logic/TextLogic.cs b/Poc.TextProcessor.Business.Logic/TextLogic.cs index 9e6a329..33989ae 100644 --- a/Poc.TextProcessor.Business.Logic/TextLogic.cs +++ b/Poc.TextProcessor.Business.Logic/TextLogic.cs @@ -2,6 +2,7 @@ using Poc.TextProcessor.Business.Logic.Abstractions; using Poc.TextProcessor.Business.Logic.Base; using Poc.TextProcessor.CrossCutting.Utils.Constants; +using Poc.TextProcessor.CrossCutting.Utils.Helpers; using Poc.TextProcessor.ResourceAccess.Contracts; using Poc.TextProcessor.ResourceAccess.Contracts.Collections; using Poc.TextProcessor.ResourceAccess.Mappers; @@ -18,11 +19,16 @@ public class TextLogic(ITextRepository textRepository, ITextMapper textMapper) : public Text Get(int id) { var textDomain = _textRepository.Get(id); + HttpStatusHelper.NotFoundException(textDomain, nameof(textDomain)); + return _textMapper.Map(textDomain); } public void Remove(int id) { + var textDomain = _textRepository.Get(id); + HttpStatusHelper.NotFoundException(textDomain, nameof(textDomain)); + _textRepository.Remove(id); } diff --git a/Poc.TextProcessor.CrossCutting.Utils/Helpers/HttpStatusHelper.cs b/Poc.TextProcessor.CrossCutting.Utils/Helpers/HttpStatusHelper.cs new file mode 100644 index 0000000..6413a34 --- /dev/null +++ b/Poc.TextProcessor.CrossCutting.Utils/Helpers/HttpStatusHelper.cs @@ -0,0 +1,13 @@ +namespace Poc.TextProcessor.CrossCutting.Utils.Helpers +{ + public static class HttpStatusHelper + { + public static void NotFoundException(object entity, string entityName) + { + if (entity == null) + { + throw new KeyNotFoundException($"{entityName} not found."); + } + } + } +} diff --git a/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Core/Settings/Endpoints.cs b/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Core/Settings/Endpoints.cs index 2862fab..349db87 100644 --- a/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Core/Settings/Endpoints.cs +++ b/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Core/Settings/Endpoints.cs @@ -22,6 +22,10 @@ public static class Text public static string GetAll => $"{BaseEndpoint}/GetAll"; #endregion + #region Delete Text Endpoint + public static string Delete(int id) => $"{BaseEndpoint}/Delete?id={id}"; + #endregion + #region Statitics Endpoints public static string StatisticsEndpoint(string text) => $"{BaseEndpoint}/Statistics?textToAnalyze={text}"; public static string StatisticsInvalidEndpoint => $"{BaseEndpoint}/Statistics////"; diff --git a/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/Endpoints/Text/DeleteTests.cs b/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/Endpoints/Text/DeleteTests.cs new file mode 100644 index 0000000..c4266d2 --- /dev/null +++ b/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/Endpoints/Text/DeleteTests.cs @@ -0,0 +1,25 @@ +using Poc.TextProcessor.IntegrityAssurance.Core.Contracts.Collections; +using Poc.TextProcessor.IntegrityAssurance.Core.Settings; +using Poc.TextProcessor.IntegrityAssurance.Tests.Endpoints.Base; +using Poc.TextProcessor.IntegrityAssurance.Tests.Helpers; +using System.Net; + +namespace Poc.TextProcessor.IntegrityAssurance.Tests.Endpoints.Text +{ + public class DeleteTests : TestsBase + { + [Test] + public async Task Delete_When_Id_Exists_Should_Return_NoContent() + { + var existingId = 10003; + + var deleteRequest = new RestRequest(Core.Settings.Endpoints.Text.Delete(existingId), Method.Delete); + var deleteResponse = await _client.ExecuteAsync(deleteRequest); + var getRequest = new RestRequest(Core.Settings.Endpoints.Text.Get(existingId), Method.Get); + var getResponse = await _client.ExecuteAsync(getRequest); + + Assert.That(deleteResponse.StatusCode, Is.EqualTo(HttpStatusCode.NoContent)); + Assert.That(getResponse.Data, Is.Null); + } + } +} \ No newline at end of file From e53cf591d9fe59f9251f0317d2dfe4133508c97d Mon Sep 17 00:00:00 2001 From: Mauro Gioberti Date: Mon, 19 Aug 2024 00:21:32 +0200 Subject: [PATCH 08/10] Add BadRequest and NotFound Tests --- .../Settings/Endpoints.cs | 12 ++++++--- .../Endpoints/Text/DeleteTests.cs | 26 ++++++++++++++---- .../Endpoints/Text/GetTests.cs | 27 +++++++++++++++---- .../Controllers/TextController.cs | 2 ++ Poc.TextProcessor.Services/TextService.cs | 8 ++++++ 5 files changed, 61 insertions(+), 14 deletions(-) diff --git a/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Core/Settings/Endpoints.cs b/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Core/Settings/Endpoints.cs index 349db87..a90abe6 100644 --- a/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Core/Settings/Endpoints.cs +++ b/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Core/Settings/Endpoints.cs @@ -18,12 +18,16 @@ public static class Text #endregion #region Get Text Endpoints - public static string Get(int id) => $"{BaseEndpoint}/Get?id={id}"; - public static string GetAll => $"{BaseEndpoint}/GetAll"; + public static string GetEndpoint(int id) => $"{BaseEndpoint}/Get?id={id}"; + public static string GetInvalidEndpointEndpoint => $"{BaseEndpoint}/Get////"; + public static string GetIdTooLongEndpoint => $"{BaseEndpoint}/Get?id=4556566545656655644654656454566545645"; + public static string GetAllEndpoint => $"{BaseEndpoint}/GetAll"; #endregion - #region Delete Text Endpoint - public static string Delete(int id) => $"{BaseEndpoint}/Delete?id={id}"; + #region Delete Text Endpoints + public static string DeleteEndpoint(int id) => $"{BaseEndpoint}/Delete?id={id}"; + public static string DeleteInvalidEndpoint => $"{BaseEndpoint}/Delete////"; + public static string DeleteIdTooLongParametersEndpoint => $"{BaseEndpoint}/Delete?id=4556566545656655644654656454566545645"; #endregion #region Statitics Endpoints diff --git a/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/Endpoints/Text/DeleteTests.cs b/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/Endpoints/Text/DeleteTests.cs index c4266d2..5606009 100644 --- a/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/Endpoints/Text/DeleteTests.cs +++ b/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/Endpoints/Text/DeleteTests.cs @@ -1,7 +1,4 @@ -using Poc.TextProcessor.IntegrityAssurance.Core.Contracts.Collections; -using Poc.TextProcessor.IntegrityAssurance.Core.Settings; using Poc.TextProcessor.IntegrityAssurance.Tests.Endpoints.Base; -using Poc.TextProcessor.IntegrityAssurance.Tests.Helpers; using System.Net; namespace Poc.TextProcessor.IntegrityAssurance.Tests.Endpoints.Text @@ -13,13 +10,32 @@ public async Task Delete_When_Id_Exists_Should_Return_NoContent() { var existingId = 10003; - var deleteRequest = new RestRequest(Core.Settings.Endpoints.Text.Delete(existingId), Method.Delete); + var deleteRequest = new RestRequest(Core.Settings.Endpoints.Text.DeleteEndpoint(existingId), Method.Delete); var deleteResponse = await _client.ExecuteAsync(deleteRequest); - var getRequest = new RestRequest(Core.Settings.Endpoints.Text.Get(existingId), Method.Get); + + var getRequest = new RestRequest(Core.Settings.Endpoints.Text.GetEndpoint(existingId), Method.Get); var getResponse = await _client.ExecuteAsync(getRequest); Assert.That(deleteResponse.StatusCode, Is.EqualTo(HttpStatusCode.NoContent)); Assert.That(getResponse.Data, Is.Null); } + + [Test] + public async Task Delete_When_Invalid_Input_Should_Return_BadRequest() + { + var request = new RestRequest(Core.Settings.Endpoints.Text.DeleteIdTooLongParametersEndpoint, Method.Delete); + var response = await _client.ExecuteAsync(request); + + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task Delete_When_Invalid_Input_Should_Return_NotFound() + { + var request = new RestRequest(Core.Settings.Endpoints.Text.DeleteInvalidEndpoint, Method.Delete); + var response = await _client.ExecuteAsync(request); + + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } } } \ No newline at end of file diff --git a/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/Endpoints/Text/GetTests.cs b/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/Endpoints/Text/GetTests.cs index 39f2f3f..f80edc7 100644 --- a/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/Endpoints/Text/GetTests.cs +++ b/Poc.TextProcessor.IntegrityAssurance/Poc.TextProcessor.IntegrityAssurance.Tests/Endpoints/Text/GetTests.cs @@ -13,7 +13,7 @@ public class GetTests : TestsBase [Test] public async Task GetAll_When_Called_Should_Return_Ok() { - var request = new RestRequest(Core.Settings.Endpoints.Text.GetAll, Method.Get); + var request = new RestRequest(Core.Settings.Endpoints.Text.GetAllEndpoint, Method.Get); var response = await _client.ExecuteAsync(request); Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); @@ -23,7 +23,7 @@ public async Task GetAll_When_Called_Should_Return_Ok() [Test] public async Task GetAll_When_Called_Should_Return_Inserted_Text() { - var request = new RestRequest(Core.Settings.Endpoints.Text.GetAll, Method.Get); + var request = new RestRequest(Core.Settings.Endpoints.Text.GetAllEndpoint, Method.Get); var response = await _client.ExecuteAsync(request); var textCollectionResponse = response.Data; @@ -43,11 +43,10 @@ public async Task GetAll_When_Called_Should_Return_Inserted_Text() Assert.That(textItem.Content, Is.EqualTo(expectedTextContent)); } - [Test] public async Task Get_When_Called_Should_Return_Ok() { - var request = new RestRequest($"{Core.Settings.Endpoints.Text.Get(ExpectedGetId)}", Method.Get); + var request = new RestRequest($"{Core.Settings.Endpoints.Text.GetEndpoint(ExpectedGetId)}", Method.Get); var response = await _client.ExecuteAsync(request); Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); @@ -59,7 +58,7 @@ public async Task Get_When_Called_Should_Return_Inserted_Text() { var expectedTextContent = "Test Result for Get by Id."; - var request = new RestRequest($"{Core.Settings.Endpoints.Text.Get(ExpectedGetId)}", Method.Get); + var request = new RestRequest($"{Core.Settings.Endpoints.Text.GetEndpoint(ExpectedGetId)}", Method.Get); var response = await _client.ExecuteAsync(request); var textItem = response.Data; @@ -69,5 +68,23 @@ public async Task Get_When_Called_Should_Return_Inserted_Text() Assert.That(textItem.Content, Is.EqualTo(expectedTextContent)); } + + [Test] + public async Task Get_When_Invalid_Input_Should_Return_BadRequest() + { + var request = new RestRequest(Core.Settings.Endpoints.Text.GetIdTooLongEndpoint, Method.Get); + var response = await _client.ExecuteAsync(request); + + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task Get_When_Invalid_Input_Should_Return_NotFound() + { + var request = new RestRequest(Core.Settings.Endpoints.Text.GetInvalidEndpointEndpoint, Method.Get); + var response = await _client.ExecuteAsync(request); + + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } } } \ No newline at end of file diff --git a/Poc.TextProcessor.Presentation.RestApi/Controllers/TextController.cs b/Poc.TextProcessor.Presentation.RestApi/Controllers/TextController.cs index 71ba828..6c7a7c7 100644 --- a/Poc.TextProcessor.Presentation.RestApi/Controllers/TextController.cs +++ b/Poc.TextProcessor.Presentation.RestApi/Controllers/TextController.cs @@ -29,6 +29,7 @@ public IActionResult GetAll() [HttpGet("Get")] [Produces("application/json", "application/xml")] + [ResponseOnException(HttpStatusCode.NotFound, typeof(KeyNotFoundException))] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Text))] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] @@ -40,6 +41,7 @@ public IActionResult Get(int id) [HttpDelete("Delete")] [Produces("application/json", "application/xml")] + [ResponseOnException(HttpStatusCode.NotFound, typeof(KeyNotFoundException))] [ProducesResponseType(StatusCodes.Status204NoContent, Type = typeof(Text))] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] diff --git a/Poc.TextProcessor.Services/TextService.cs b/Poc.TextProcessor.Services/TextService.cs index a8bdf70..7167d7e 100644 --- a/Poc.TextProcessor.Services/TextService.cs +++ b/Poc.TextProcessor.Services/TextService.cs @@ -16,6 +16,10 @@ public Text Get(int id) { return _textLogic.Get(id); } + catch (KeyNotFoundException) + { + throw; + } catch (Exception e) { HandleException(e); @@ -30,6 +34,10 @@ public void Remove(int id) { _textLogic.Remove(id); } + catch (KeyNotFoundException) + { + throw; + } catch (Exception e) { HandleException(e); From d0bb749d082532ca5c2d6723842b71855e467acd Mon Sep 17 00:00:00 2001 From: Mauro Gioberti Date: Mon, 19 Aug 2024 01:02:18 +0200 Subject: [PATCH 09/10] Update Seed Texts Script --- .../Scripts/{01-AddTextValue.sql => 01-SeedTextsData.sql} | 2 ++ 1 file changed, 2 insertions(+) rename Poc.TextProcessor.Presentation.RestApi/IntegrityAssurance/Scripts/{01-AddTextValue.sql => 01-SeedTextsData.sql} (93%) diff --git a/Poc.TextProcessor.Presentation.RestApi/IntegrityAssurance/Scripts/01-AddTextValue.sql b/Poc.TextProcessor.Presentation.RestApi/IntegrityAssurance/Scripts/01-SeedTextsData.sql similarity index 93% rename from Poc.TextProcessor.Presentation.RestApi/IntegrityAssurance/Scripts/01-AddTextValue.sql rename to Poc.TextProcessor.Presentation.RestApi/IntegrityAssurance/Scripts/01-SeedTextsData.sql index ae3611b..c16ab0b 100644 --- a/Poc.TextProcessor.Presentation.RestApi/IntegrityAssurance/Scripts/01-AddTextValue.sql +++ b/Poc.TextProcessor.Presentation.RestApi/IntegrityAssurance/Scripts/01-SeedTextsData.sql @@ -1,3 +1,5 @@ +USE [PocTextProcessor]; + SET IDENTITY_INSERT [dbo].[Texts] ON; INSERT INTO [dbo].[Texts]([Id], [Content]) VALUES (10001, N'Test Expected Result.'); From 8dbe5f5a9c8e4890139f34170cba91aa913dfe49 Mon Sep 17 00:00:00 2001 From: Mauro Gioberti Date: Thu, 29 Aug 2024 12:52:10 +0200 Subject: [PATCH 10/10] Add new endpoints to the HTTP test file --- .../Poc.TextProcessor.Presentation.RestApi.http | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Poc.TextProcessor.Presentation.RestApi/Poc.TextProcessor.Presentation.RestApi.http b/Poc.TextProcessor.Presentation.RestApi/Poc.TextProcessor.Presentation.RestApi.http index 4386e78..7f28169 100644 --- a/Poc.TextProcessor.Presentation.RestApi/Poc.TextProcessor.Presentation.RestApi.http +++ b/Poc.TextProcessor.Presentation.RestApi/Poc.TextProcessor.Presentation.RestApi.http @@ -1,5 +1,17 @@ @Poc.TextProcessor.Presentation.RestApi_HostAddress = http://localhost:5162 +### Get all texts +GET {{Poc.TextProcessor.Presentation.RestApi_HostAddress}}/Text/GetAll +Accept: application/json + +### Retrieve text by ID +GET {{Poc.TextProcessor.Presentation.RestApi_HostAddress}}/Text/Get?id=1 +Accept: application/json + +### Delete text by ID +DELETE {{Poc.TextProcessor.Presentation.RestApi_HostAddress}}/Text/Delete?id=1 +Accept: application/json + ### Retrieve available text processing options GET {{Poc.TextProcessor.Presentation.RestApi_HostAddress}}/Text/Options Accept: application/json