diff --git a/VeloBasar.sln b/VeloBasar.sln index 6986a942..e9e296b4 100644 --- a/VeloBasar.sln +++ b/VeloBasar.sln @@ -9,7 +9,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BraunauMobil.VeloBasar.Test EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BraunauMobil.VeloBasar.DataGenerator", "test\BraunauMobil.VeloBasar.DataGenerator\BraunauMobil.VeloBasar.DataGenerator.csproj", "{4F4A648F-A253-48B4-8E7E-9CD51EEB39FA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BraunauMobil.VeloBasar.StatusPushTestServer", "test\BraunauMobil.VeloBasar.StatusPushTestServer\BraunauMobil.VeloBasar.StatusPushTestServer.csproj", "{4D4402FE-60B7-43ED-9277-337D0433E867}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BraunauMobil.VeloBasar.StatusPushTestServer", "test\BraunauMobil.VeloBasar.StatusPushTestServer\BraunauMobil.VeloBasar.StatusPushTestServer.csproj", "{4D4402FE-60B7-43ED-9277-337D0433E867}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BraunauMobil.VeloBasar.IntegrationTests", "test\BraunauMobil.VeloBasar.IntegrationTests\BraunauMobil.VeloBasar.IntegrationTests.csproj", "{B0860A22-2C22-4FA3-B272-658AC624F871}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -33,6 +35,10 @@ Global {4D4402FE-60B7-43ED-9277-337D0433E867}.Debug|Any CPU.Build.0 = Debug|Any CPU {4D4402FE-60B7-43ED-9277-337D0433E867}.Release|Any CPU.ActiveCfg = Release|Any CPU {4D4402FE-60B7-43ED-9277-337D0433E867}.Release|Any CPU.Build.0 = Release|Any CPU + {B0860A22-2C22-4FA3-B272-658AC624F871}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B0860A22-2C22-4FA3-B272-658AC624F871}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B0860A22-2C22-4FA3-B272-658AC624F871}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B0860A22-2C22-4FA3-B272-658AC624F871}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/BraunauMobil.VeloBasar/Program.cs b/src/BraunauMobil.VeloBasar/Program.cs index 06dc1a8a..e1d3c087 100644 --- a/src/BraunauMobil.VeloBasar/Program.cs +++ b/src/BraunauMobil.VeloBasar/Program.cs @@ -24,7 +24,9 @@ namespace BraunauMobil.VeloBasar; -public static class Program +#pragma warning disable CA1052 // Static holder types should be Static or NotInheritable +public class Program +#pragma warning restore CA1052 // Static holder types should be Static or NotInheritable { public static async Task Main(string[] args) { diff --git a/src/BraunauMobil.VeloBasar/appsettings.IntegrationTests.json b/src/BraunauMobil.VeloBasar/appsettings.IntegrationTests.json new file mode 100644 index 00000000..6e43546d --- /dev/null +++ b/src/BraunauMobil.VeloBasar/appsettings.IntegrationTests.json @@ -0,0 +1,57 @@ +{ + "ApplicationSettings": { + "Culture": "en-US" + }, + "Logging": { + "LogLevel": { + "Default": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "WordPressStatusPushSettings": { + "Enabled": false, + "EndpointUrl": null, + "ApiKey": null + }, + "PrintSettings": { + "Acceptance": { + "SignatureText": "For XYZ:", + "SubTitle": "We accepted these products:", + "TokenTitle": "Online-Query-Code:", + "TransactionType": 0, + "TitleFormat": "XYZ - {0} : Acceptance receipt #{1}", + "StatusLinkFormat": "statusLink={0}" + }, + "Sale": { + "FooterText": "Thanks!", + "HintText": "Please be aware of.", + "SellerInfoText": "*Seller contact", + "SubTitle": "We sold theses products:", + "TransactionType": 2, + "TitleFormat": "XYZ - {0} : Sale receipt #{1}" + }, + "Settlement": { + "ConfirmationText": "I confirm.", + "SignatureText": "Signature", + "SoldTitle": "We sold these products:", + "NotSoldTitle": "We could not sell these products:", + "TransactionType": 3, + "TitleFormat": "XYZ - {0} : Settlement #{1}", + "BankTransactionTextFormat": "XYZ - Revenue {0}" + }, + "Label": { + "MaxDescriptionLength": 150, + "TitleFormat": "XYZ - {0}" + }, + "PageMargins": { + "Left": 20, + "Top": 20, + "Right": 20, + "Bottom": 20 + }, + "BannerFilePath": "", + "BannerSubtitle": "XYZ Address", + "Website": "XYZ website", + "QrCodeLengthMillimeters": 50 + } +} diff --git a/test/BraunauMobil.VeloBasar.IntegrationTests/AngleSharpExtensions.cs b/test/BraunauMobil.VeloBasar.IntegrationTests/AngleSharpExtensions.cs new file mode 100644 index 00000000..8eb8e528 --- /dev/null +++ b/test/BraunauMobil.VeloBasar.IntegrationTests/AngleSharpExtensions.cs @@ -0,0 +1,67 @@ +using AngleSharp.Dom; + +namespace BraunauMobil.VeloBasar.IntegrationTests; + +public static class AngleSharpExtensions +{ + public static IHtmlAnchorElement QueryAnchorByText(this IParentNode node, string text) + { + ArgumentNullException.ThrowIfNull(node); + ArgumentNullException.ThrowIfNull(text); + + return node.QueryElement("a", button => button.InnerHtml == text); + } + + public static IHtmlButtonElement QueryButtonByText(this IParentNode node, string text) + + { + ArgumentNullException.ThrowIfNull(node); + ArgumentNullException.ThrowIfNull(text); + + return node.QueryElement("button", button => button.InnerHtml == text); + } + + public static IHtmlAnchorElement QueryTableDetailsLink(this IParentNode node, int rowId) + { + ArgumentNullException.ThrowIfNull(node); + + IHtmlTableElement? table = node.QuerySelector("table"); + table.Should().NotBeNull(); + IHtmlTableRowElement headerRow = table!.Rows[0]; + INode idHeaderCell = headerRow.Cells.Should().ContainSingle(cell => cell.TextContent == "Id").Subject; + int idColumnIndex = headerRow.Cells.Index(idHeaderCell); + + IHtmlTableRowElement row = table.Rows.Should().ContainSingle(row => row.Cells[idColumnIndex].TextContent == rowId.ToString()).Subject; + return row.QueryAnchorByText("Details"); + } + + public static IHtmlFormElement QueryForm(this IParentNode node, string selector = "form") + { + ArgumentNullException.ThrowIfNull(node); + ArgumentNullException.ThrowIfNull(selector); + + return node.QuerySelector(selector) + .Should().BeAssignableTo().Subject; + } + + public static TElement QueryElement(this IParentNode node, string selector) + { + ArgumentNullException.ThrowIfNull(node); + ArgumentNullException.ThrowIfNull(selector); + + return node.QuerySelector(selector) + .Should().BeAssignableTo().Subject; + } + + public static TElement QueryElement(this IParentNode node, string selector, Func predicate) + { + ArgumentNullException.ThrowIfNull(node); + ArgumentNullException.ThrowIfNull(selector); + + TElement? element = node.QuerySelectorAll(selector) + .OfType() + .FirstOrDefault(predicate); + element.Should().NotBeNull(); + return element!; + } +} diff --git a/test/BraunauMobil.VeloBasar.IntegrationTests/BraunauMobil.VeloBasar.IntegrationTests.csproj b/test/BraunauMobil.VeloBasar.IntegrationTests/BraunauMobil.VeloBasar.IntegrationTests.csproj new file mode 100644 index 00000000..2adc9d0b --- /dev/null +++ b/test/BraunauMobil.VeloBasar.IntegrationTests/BraunauMobil.VeloBasar.IntegrationTests.csproj @@ -0,0 +1,39 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/test/BraunauMobil.VeloBasar.IntegrationTests/CustomWebApplicationFactory.cs b/test/BraunauMobil.VeloBasar.IntegrationTests/CustomWebApplicationFactory.cs new file mode 100644 index 00000000..9121e5c2 --- /dev/null +++ b/test/BraunauMobil.VeloBasar.IntegrationTests/CustomWebApplicationFactory.cs @@ -0,0 +1,67 @@ +using BraunauMobil.VeloBasar.BusinessLogic; +using BraunauMobil.VeloBasar.Data; +using BraunauMobil.VeloBasar.IntegrationTests.Mockups; +using BraunauMobil.VeloBasar.Pdf; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using System.Data.Common; +using Xan.Extensions; + +namespace BraunauMobil.VeloBasar.IntegrationTests; + +public class CustomWebApplicationFactory + : WebApplicationFactory +{ + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.ConfigureServices(services => + { + ServiceDescriptor dbContextDescriptor = services.Single(d => d.ServiceType == typeof(DbContextOptions)); + services.Remove(dbContextDescriptor); + + ServiceDescriptor? dbConnectionDescriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbConnection)); + if (dbConnectionDescriptor is not null) + { + services.Remove(dbConnectionDescriptor); + } + + // Create open SqliteConnection so EF won't automatically close it. + services.AddSingleton(container => + { + SqliteConnection connection = new ("DataSource=:memory:"); + connection.Open(); + + return connection; + }); + + services.AddDbContext((container, options) => + { + DbConnection connection = container.GetRequiredService(); + options.UseSqlite(connection); + }); + + ServiceDescriptor clockDescriptor = services.Single(d => d.ServiceType == typeof(IClock)); + services.Remove(clockDescriptor); + services.AddSingleton(X.Clock); + + ServiceDescriptor appContextDescriptor = services.Single(d => d.ServiceType == typeof(IAppContext)); + services.Remove(appContextDescriptor); + services.AddScoped(); + + ServiceDescriptor setupServiceDescriptor = services.Single(d => d.ServiceType == typeof(ISetupService)); + services.Remove(setupServiceDescriptor); + services.AddScoped(); + + ServiceDescriptor transactionDocumentGeneratorDescriptor = services.Single(d => d.ServiceType == typeof(ITransactionDocumentGenerator)); + services.Remove(transactionDocumentGeneratorDescriptor); + services.AddScoped(); + + ServiceDescriptor productLabelGeneratorDescriptor = services.Single(d => d.ServiceType == typeof(IProductLabelGenerator)); + services.Remove(productLabelGeneratorDescriptor); + services.AddScoped(); + }); + + builder.UseEnvironment("IntegrationTests"); + } +} diff --git a/test/BraunauMobil.VeloBasar.IntegrationTests/GlobalUsings.cs b/test/BraunauMobil.VeloBasar.IntegrationTests/GlobalUsings.cs new file mode 100644 index 00000000..69b25b32 --- /dev/null +++ b/test/BraunauMobil.VeloBasar.IntegrationTests/GlobalUsings.cs @@ -0,0 +1,4 @@ +global using AngleSharp.Html.Dom; +global using BraunauMobil.VeloBasar.Models.Documents; +global using FluentAssertions; +global using Xunit; \ No newline at end of file diff --git a/test/BraunauMobil.VeloBasar.IntegrationTests/HttpClientExtensions.cs b/test/BraunauMobil.VeloBasar.IntegrationTests/HttpClientExtensions.cs new file mode 100644 index 00000000..ff02162f --- /dev/null +++ b/test/BraunauMobil.VeloBasar.IntegrationTests/HttpClientExtensions.cs @@ -0,0 +1,158 @@ +using AngleSharp.Dom; +using AngleSharp.Io; +using BraunauMobil.VeloBasar.Models.Documents; + +namespace BraunauMobil.VeloBasar.IntegrationTests; + +public static class HttpClientExtensions +{ + public static async Task GetDocumentAsync(this HttpClient client, string requestUri) + { + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(requestUri); + + HttpResponseMessage response = await client.GetAsync(requestUri); + response.EnsureSuccessStatusCode(); + return await response.GetDocumentAsync(); + } + + public static async Task GetDocumentAsync(this HttpClient client, HttpRequestMessage request) + { + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(request); + + HttpResponseMessage response = await client.SendAsync(request); + response.EnsureSuccessStatusCode(); + return await response.GetDocumentAsync(); + } + + public static async Task GetAcceptanceDocumentAsync(this HttpClient client, string requestUri) + { + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(requestUri); + + return await client.GetJsonDocumentAsync(requestUri); + } + + public static async Task GetSaleDocumentAsync(this HttpClient client, string requestUri) + { + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(requestUri); + + return await client.GetJsonDocumentAsync(requestUri); + } + + public static async Task GetSettlementDocumentAsync(this HttpClient client, string requestUri) + { + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(requestUri); + + return await client.GetJsonDocumentAsync(requestUri); + } + + public static async Task GetJsonDocumentAsync(this HttpClient client, string requestUri) + { + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(requestUri); + + HttpResponseMessage response = await client.GetAsync(requestUri); + response.EnsureSuccessStatusCode(); + + response.Content.Headers.ContentType.Should().NotBeNull(); + response.Content.Headers.ContentType!.MediaType.Should().Be("application/pdf"); + string json = await response.Content.ReadAsStringAsync(); + + return json.DeserializeFromJson(); + } + + public static async Task NavigateMenuAsync(this HttpClient client, string menuEntryText) + { + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(menuEntryText); + + IHtmlDocument indexDocument = await client.GetDocumentAsync(""); + return await client.NavigateMenuAsync(indexDocument, menuEntryText); + } + + public static async Task NavigateMenuAsync(this HttpClient client, IHtmlDocument menuDocument, string menuEntryText) + { + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(menuDocument); + ArgumentNullException.ThrowIfNull(menuEntryText); + + IHtmlAnchorElement menuEntryAnchor = menuDocument.QueryAnchorByText(menuEntryText); + return await client.GetDocumentAsync(menuEntryAnchor.Href); + } + + public static async Task SendFormAsync(this HttpClient client, IHtmlFormElement form, IHtmlElement button) + { + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(button); + ArgumentNullException.ThrowIfNull(form); + + return await client.SendFormAsync(form, button, []); + } + + public static async Task SendFormAsync(this HttpClient client, IHtmlFormElement form, IHtmlElement button, IEnumerable> formValues) + { + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(button); + ArgumentNullException.ThrowIfNull(form); + ArgumentNullException.ThrowIfNull(formValues); + + foreach (var (key, value) in formValues) + { + IElement? formElement = form[key]; + if (formElement is IHtmlInputElement input) + { + if (input.Type == "checkbox") + { + input.IsChecked = value.Should().BeOfType().Subject; + } + else + { + input.Value = value.ToString() ?? throw new NullReferenceException($"String value for element {key} is null."); + } + } + else if (formElement is IHtmlSelectElement select) + { + string selectedValue = value.ToString() ?? throw new NullReferenceException($"String value for element {key} is null."); + foreach (IHtmlOptionElement option in select.Options) + { + option.IsSelected = option.Value == selectedValue; + } + } + else if (formElement is null) + { + throw new InvalidOperationException($"No form element found for value {key}({value})."); + } + else + { + throw new InvalidOperationException($"Form element for value {key}({value}) has an unsupported type: {formElement.GetType()}"); + } + } + + DocumentRequest? submit = form.GetSubmission(button); + Uri target = (Uri)submit!.Target; + if (button.HasAttribute("formaction")) + { + string? formaction = button.GetAttribute("formaction"); + if (formaction is not null) + { + target = new Uri(formaction, UriKind.Relative); + } + } + HttpRequestMessage submission = new(new System.Net.Http.HttpMethod(submit.Method.ToString()), target) + { + Content = new StreamContent(submit.Body) + }; + + foreach (var (key, value) in submit.Headers) + { + submission.Headers.TryAddWithoutValidation(key, value); + submission.Content.Headers.TryAddWithoutValidation(key, value); + } + + return await client.GetDocumentAsync(submission); + } +} diff --git a/test/BraunauMobil.VeloBasar.IntegrationTests/HttpResponseMessageExtensions.cs b/test/BraunauMobil.VeloBasar.IntegrationTests/HttpResponseMessageExtensions.cs new file mode 100644 index 00000000..fde9fb80 --- /dev/null +++ b/test/BraunauMobil.VeloBasar.IntegrationTests/HttpResponseMessageExtensions.cs @@ -0,0 +1,41 @@ +using AngleSharp; +using AngleSharp.Io; +using System.Net.Http.Headers; + +namespace BraunauMobil.VeloBasar.IntegrationTests; + +public static class HttpResponseMessageExtensions +{ + public static async Task GetDocumentAsync(this HttpResponseMessage response) + { + ArgumentNullException.ThrowIfNull(response); + + string content = await response.Content.ReadAsStringAsync(); + var document = await BrowsingContext.New() + .OpenAsync(ResponseFactory, CancellationToken.None); + return (IHtmlDocument)document; + + void ResponseFactory(VirtualResponse htmlResponse) + { + htmlResponse + .Address(response.RequestMessage!.RequestUri) + .Status(response.StatusCode); + + MapHeaders(response.Headers); + MapHeaders(response.Content.Headers); + + htmlResponse.Content(content); + + void MapHeaders(HttpHeaders headers) + { + foreach (var header in headers) + { + foreach (var value in header.Value) + { + htmlResponse.Header(header.Key, value); + } + } + } + } + } +} diff --git a/test/BraunauMobil.VeloBasar.IntegrationTests/ID.cs b/test/BraunauMobil.VeloBasar.IntegrationTests/ID.cs new file mode 100644 index 00000000..61a940b3 --- /dev/null +++ b/test/BraunauMobil.VeloBasar.IntegrationTests/ID.cs @@ -0,0 +1,42 @@ +namespace BraunauMobil.VeloBasar.IntegrationTests; + +public static class ID +{ + public const int FirstBasar = 1; + public const int SecondBasar = 2; + + public static class Countries + { + public const int Austria = 1; + public const int Germany = 2; + } + + public static class ProductTypes + { + public const int Unicycle = 1; + public const int RoadBike = 2; + public const int MansCityBike = 3; + public const int WomansCityBike = 4; + public const int ChildrensBike = 5; + public const int Scooter = 6; + public const int EBike = 7; + public const int SteelSteed = 8; + } + + public static class Sellers + { + public const int SchattenfellMagsame = 1; + } + + public static class Products + { + public static class FirstBasar + { + public static class SchattenfellMagsame + { + public const int Votec = 1; + public const int KTM = 2; + } + } + } +} diff --git a/test/BraunauMobil.VeloBasar.IntegrationTests/JsonExtensionsHelpers.cs b/test/BraunauMobil.VeloBasar.IntegrationTests/JsonExtensionsHelpers.cs new file mode 100644 index 00000000..3da1795b --- /dev/null +++ b/test/BraunauMobil.VeloBasar.IntegrationTests/JsonExtensionsHelpers.cs @@ -0,0 +1,24 @@ +using System.Text; +using System.Text.Json; + +namespace BraunauMobil.VeloBasar.IntegrationTests; + +public static class JsonExtensionsHelpers +{ + public static T DeserializeFromJson(this string json) + { + ArgumentNullException.ThrowIfNull(json); + + T? deserialized = JsonSerializer.Deserialize(json); + deserialized.Should().NotBeNull(); + return deserialized!; + } + + public static byte[] SerializeAsJson(this T obj) + { + ArgumentNullException.ThrowIfNull(obj); + + string json = JsonSerializer.Serialize(obj); + return Encoding.UTF8.GetBytes(json); + } +} diff --git a/test/BraunauMobil.VeloBasar.IntegrationTests/Mockups/AppContextWrapper.cs b/test/BraunauMobil.VeloBasar.IntegrationTests/Mockups/AppContextWrapper.cs new file mode 100644 index 00000000..f7948bac --- /dev/null +++ b/test/BraunauMobil.VeloBasar.IntegrationTests/Mockups/AppContextWrapper.cs @@ -0,0 +1,33 @@ +using BraunauMobil.VeloBasar.Data; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; + +namespace BraunauMobil.VeloBasar.IntegrationTests.Mockups; + +public class AppContextWrapper(IWebHostEnvironment env, VeloDbContext db) + : IAppContext +{ + private readonly VeloBasarAppContext _actual = new(env, db); + + public string Version => _actual.Version; + + public bool DevToolsEnabled() + => _actual.DevToolsEnabled(); + + public async Task NeedsInitialSetupAsync() + { + try + { + await db.Users.AnyAsync(); + } + catch (SqliteException) + { + // If an exception is thrown, the users table does not exist, therefore the database needs to be initialized + return true; + } + return false; + } + + public async Task NeedsMigrationAsync() + => await Task.FromResult(false); +} diff --git a/test/BraunauMobil.VeloBasar.IntegrationTests/Mockups/ClockMock.cs b/test/BraunauMobil.VeloBasar.IntegrationTests/Mockups/ClockMock.cs new file mode 100644 index 00000000..0df8230d --- /dev/null +++ b/test/BraunauMobil.VeloBasar.IntegrationTests/Mockups/ClockMock.cs @@ -0,0 +1,29 @@ +using Xan.Extensions; + +namespace BraunauMobil.VeloBasar.IntegrationTests.Mockups; + +public class ClockMock + : IClock +{ + public ClockMock() + { } + + public ClockMock(DateTime now) + { + Now = new DateTimeOffset(now); + } + + public DateTimeOffset Now { get; set; } + + public DateOnly GetCurrentDate() + => DateOnly.FromDateTime(Now.DateTime); + + public DateTime GetCurrentDateTime() + => Now.DateTime; + + public DateTimeOffset GetCurrentDateTimeOffset() + => Now; + + public TimeOnly GetCurrentTime() + => TimeOnly.FromDateTime(Now.DateTime); +} diff --git a/test/BraunauMobil.VeloBasar.IntegrationTests/Mockups/JsonProductLabelGenerator.cs b/test/BraunauMobil.VeloBasar.IntegrationTests/Mockups/JsonProductLabelGenerator.cs new file mode 100644 index 00000000..895323ee --- /dev/null +++ b/test/BraunauMobil.VeloBasar.IntegrationTests/Mockups/JsonProductLabelGenerator.cs @@ -0,0 +1,22 @@ +using BraunauMobil.VeloBasar.Models.Documents; +using BraunauMobil.VeloBasar.Pdf; + +namespace BraunauMobil.VeloBasar.IntegrationTests.Mockups; + +public class JsonProductLabelGenerator + : IProductLabelGenerator +{ + public async Task CreateLabelAsync(ProductLabelDocumentModel model) + { + ArgumentNullException.ThrowIfNull(model); + + return await Task.FromResult(model.SerializeAsJson()); + } + + public async Task CreateLabelsAsync(IEnumerable models) + { + ArgumentNullException.ThrowIfNull(models); + + return await Task.FromResult(models.SerializeAsJson()); + } +} diff --git a/test/BraunauMobil.VeloBasar.IntegrationTests/Mockups/JsonTransactionDocumentGenerator.cs b/test/BraunauMobil.VeloBasar.IntegrationTests/Mockups/JsonTransactionDocumentGenerator.cs new file mode 100644 index 00000000..6b33c665 --- /dev/null +++ b/test/BraunauMobil.VeloBasar.IntegrationTests/Mockups/JsonTransactionDocumentGenerator.cs @@ -0,0 +1,29 @@ +using BraunauMobil.VeloBasar.Models.Documents; +using BraunauMobil.VeloBasar.Pdf; + +namespace BraunauMobil.VeloBasar.IntegrationTests.Mockups; + +public class JsonTransactionDocumentGenerator + : ITransactionDocumentGenerator +{ + public async Task CreateAcceptanceAsync(AcceptanceDocumentModel model) + { + ArgumentNullException.ThrowIfNull(model); + + return await Task.FromResult(model.SerializeAsJson()); + } + + public async Task CreateSaleAsync(SaleDocumentModel model) + { + ArgumentNullException.ThrowIfNull(model); + + return await Task.FromResult(model.SerializeAsJson()); + } + + public async Task CreateSettlementAsync(SettlementDocumentModel model) + { + ArgumentNullException.ThrowIfNull(model); + + return await Task.FromResult(model.SerializeAsJson()); + } +} diff --git a/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/SetupServiceWrapper.cs b/test/BraunauMobil.VeloBasar.IntegrationTests/Mockups/SetupServiceWrapper.cs similarity index 90% rename from test/BraunauMobil.VeloBasar.Tests/IntegrationTests/SetupServiceWrapper.cs rename to test/BraunauMobil.VeloBasar.IntegrationTests/Mockups/SetupServiceWrapper.cs index c0afe241..a94f8604 100644 --- a/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/SetupServiceWrapper.cs +++ b/test/BraunauMobil.VeloBasar.IntegrationTests/Mockups/SetupServiceWrapper.cs @@ -1,9 +1,10 @@ using BraunauMobil.VeloBasar.BusinessLogic; using BraunauMobil.VeloBasar.Data; +using BraunauMobil.VeloBasar.Models; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Localization; -namespace BraunauMobil.VeloBasar.Tests.IntegrationTests; +namespace BraunauMobil.VeloBasar.IntegrationTests.Mockups; public class SetupServiceWrapper(VeloDbContext db, UserManager userManager, IStringLocalizer localizer) : ISetupService diff --git a/test/BraunauMobil.VeloBasar.IntegrationTests/Steps/FirstBasar/AcceptSellers.cs b/test/BraunauMobil.VeloBasar.IntegrationTests/Steps/FirstBasar/AcceptSellers.cs new file mode 100644 index 00000000..46b0a7c0 --- /dev/null +++ b/test/BraunauMobil.VeloBasar.IntegrationTests/Steps/FirstBasar/AcceptSellers.cs @@ -0,0 +1,91 @@ +using BraunauMobil.VeloBasar.Models.Documents; + +namespace BraunauMobil.VeloBasar.IntegrationTests.Steps.FirstBasar; + +public class AcceptSellers(TestContext context) +{ + public async Task Run() + { + IHtmlDocument newAcceptanceDocument = await context.HttpClient.NavigateMenuAsync("New Acceptance"); + newAcceptanceDocument.Title.Should().Be("Acceptance - Enter seller - Velo Basar"); + + IHtmlFormElement form = newAcceptanceDocument.QueryForm(); + IHtmlButtonElement submitButton = newAcceptanceDocument.QueryButtonByText("Continue"); + + IHtmlDocument enterProductsDocument = await context.HttpClient.SendFormAsync(form, submitButton, new Dictionary + { + { "FirstName", "Schattenfell" }, + { "LastName", "Magsame" }, + { "CountryId", ID.Countries.Germany }, + { "ZIP", "6295" }, + { "City", "Hobbingen" }, + { "Street", "Heidenschuss 41" }, + { "PhoneNumber", "71904814" }, + { "EMail", "schattenfell@magsame.me" }, + }); + enterProductsDocument.Title.Should().Be("Acceptance for seller with ID: 1 - Enter products - Velo Basar"); + + enterProductsDocument = await EnterProduct1(enterProductsDocument); + enterProductsDocument = await EnterProduct2(enterProductsDocument); + + IHtmlAnchorElement saveAnchor = enterProductsDocument.QueryAnchorByText("Save accept session"); + + IHtmlDocument successDocument = await context.HttpClient.GetDocumentAsync(saveAnchor.Href); + successDocument.Title.Should().Be("Acceptance #1 - Velo Basar"); + + IHtmlAnchorElement voucherAnchor = successDocument.QueryAnchorByText("Voucher"); + AcceptanceDocumentModel document = await context.HttpClient.GetAcceptanceDocumentAsync(voucherAnchor.Href); + document.Should().BeEquivalentTo(context.AcceptanceDocument("XYZ - First Bazaar : Acceptance receipt #1", + "Edoras, 5/4/2063", + "Schattenfell Magsame".Line("Heidenschuss 41").Line("6295 Hobbingen").Line(), + "Seller.-ID: 1", + "statusLink=536314D61", + "536314D61", + "Edoras on Thursday, April 5, 2063 at 11:22 AM", + "2 Product", + "$191.88", + [ + new ProductTableRowDocumentModel("1", "Votec - Children's bike".Line("Votec VRC Comp").Line(" green 1067425379"), "24", "$92.99", null), + new ProductTableRowDocumentModel("2", "KTM - Steel steed".Line("Steed 1").Line(" red 1239209209"), "22", "$98.89", null), + ]) + ); + } + + private async Task EnterProduct1(IHtmlDocument enterProductsDocument) + { + IHtmlFormElement form = enterProductsDocument.QueryForm(); + IHtmlButtonElement submitButton = enterProductsDocument.QueryButtonByText("Add"); + + IHtmlDocument postDocument = await context.HttpClient.SendFormAsync(form, submitButton, new Dictionary + { + { "TypeId", ID.ProductTypes.ChildrensBike }, + { "Brand", "Votec" }, + { "Color", "green" }, + { "FrameNumber", "1067425379" }, + { "Description", "Votec VRC Comp" }, + { "TireSize", "24" }, + { "Price", 92.99m } + }); + postDocument.Title.Should().Be("Acceptance for seller with ID: 1 - Enter products - Velo Basar"); + return postDocument; + } + + private async Task EnterProduct2(IHtmlDocument enterProductsDocument) + { + IHtmlFormElement form = enterProductsDocument.QueryForm(); + IHtmlButtonElement submitButton = enterProductsDocument.QueryButtonByText("Add"); + + IHtmlDocument postDocument = await context.HttpClient.SendFormAsync(form, submitButton, new Dictionary + { + { "TypeId", ID.ProductTypes.SteelSteed }, + { "Brand", "KTM" }, + { "Color", "red" }, + { "FrameNumber", "1239209209" }, + { "Description", "Steed 1" }, + { "TireSize", "22" }, + { "Price", 98.89m } + }); + postDocument.Title.Should().Be("Acceptance for seller with ID: 1 - Enter products - Velo Basar"); + return postDocument; + } +} diff --git a/test/BraunauMobil.VeloBasar.IntegrationTests/Steps/FirstBasar/Creation.cs b/test/BraunauMobil.VeloBasar.IntegrationTests/Steps/FirstBasar/Creation.cs new file mode 100644 index 00000000..c23b75b0 --- /dev/null +++ b/test/BraunauMobil.VeloBasar.IntegrationTests/Steps/FirstBasar/Creation.cs @@ -0,0 +1,50 @@ +namespace BraunauMobil.VeloBasar.IntegrationTests.Steps.FirstBasar; + +public class Creation(TestContext context) +{ + public async Task Run() + { + await CreateBasar(); + await EditBasar(); + } + + private async Task CreateBasar() + { + IHtmlDocument basarListDocument = await context.HttpClient.NavigateMenuAsync("Bazaars"); + IHtmlAnchorElement createBasarLink = basarListDocument.QueryAnchorByText("Create Bazaar"); + + IHtmlDocument createBasarDocument = await context.HttpClient.GetDocumentAsync(createBasarLink.Href); + createBasarDocument.Title.Should().Be("Create Bazaar - Velo Basar"); + + IHtmlFormElement form = createBasarDocument.QueryForm(); + IHtmlButtonElement submitButton = createBasarDocument.QueryElement("button"); + + IHtmlDocument postDocument = await context.HttpClient.SendFormAsync(form, submitButton, new Dictionary + { + { "Date", "2063-05-04" }, + { "Name", "First Bazaar" }, + { "Location", "Edoras" }, + { "ProductCommissionPercentage", "9" }, + { "State", "Enabled" }, + }); + postDocument.Title.Should().Be("Bazaars - Velo Basar"); + } + + private async Task EditBasar() + { + IHtmlDocument basarListDocument = await context.HttpClient.NavigateMenuAsync("Bazaars"); + IHtmlAnchorElement editBasarLink = basarListDocument.QueryAnchorByText("Edit"); + + IHtmlDocument editBasarDocument = await context.HttpClient.GetDocumentAsync(editBasarLink.Href); + editBasarDocument.Title.Should().Be("Edit Bazaar - Velo Basar"); + + IHtmlFormElement form = editBasarDocument.QueryForm(); + IHtmlButtonElement submitButton = editBasarDocument.QueryElement("button"); + + IHtmlDocument postDocument = await context.HttpClient.SendFormAsync(form, submitButton, new Dictionary + { + { "ProductCommissionPercentage", "10" }, + }); + postDocument.Title.Should().Be("Bazaars - Velo Basar"); + } +} diff --git a/test/BraunauMobil.VeloBasar.IntegrationTests/Steps/FirstBasar/SellProducts.cs b/test/BraunauMobil.VeloBasar.IntegrationTests/Steps/FirstBasar/SellProducts.cs new file mode 100644 index 00000000..4e88780e --- /dev/null +++ b/test/BraunauMobil.VeloBasar.IntegrationTests/Steps/FirstBasar/SellProducts.cs @@ -0,0 +1,38 @@ +using BraunauMobil.VeloBasar.Models.Documents; + +namespace BraunauMobil.VeloBasar.IntegrationTests.Steps.FirstBasar; + +public class SellProducts(TestContext context) +{ + public async Task Run() + { + IHtmlDocument cartDocument = await context.HttpClient.NavigateMenuAsync("Cart"); + cartDocument.Title.Should().Be("New Sale - Velo Basar"); + + IHtmlFormElement form = cartDocument.QueryForm(); + IHtmlButtonElement submitButton = cartDocument.QueryButtonByText("Add"); + + cartDocument = await context.HttpClient.SendFormAsync(form, submitButton, new Dictionary + { + { "ProductId", ID.Products.FirstBasar.SchattenfellMagsame.Votec } + }); + cartDocument.Title.Should().Be("New Sale - Velo Basar"); + + form = cartDocument.QueryForm(); + IHtmlButtonElement sellButton = cartDocument.QueryButtonByText("Sell"); + IHtmlDocument successDocument = await context.HttpClient.SendFormAsync(form, sellButton); + successDocument.Title.Should().Be("Sale #1 - Velo Basar"); + + IHtmlAnchorElement voucherAnchor = successDocument.QueryAnchorByText("Voucher"); + SaleDocumentModel document = await context.HttpClient.GetSaleDocumentAsync(voucherAnchor.Href); + document.Should().BeEquivalentTo(context.SaleDocument( + "XYZ - First Bazaar : Sale receipt #1", + "Edoras, 5/4/2063", + "1 Product", + "$92.99", + [ + new ProductTableRowDocumentModel("1", "Votec - Children's bike".Line("Votec VRC Comp").Line(" green 1067425379"), "24", "$92.99", "* Schattenfell Magsame, Heidenschuss 41, 6295 Hobbingen") + ]) + ); + } +} diff --git a/test/BraunauMobil.VeloBasar.IntegrationTests/Steps/FirstBasar/SettleSellers.cs b/test/BraunauMobil.VeloBasar.IntegrationTests/Steps/FirstBasar/SettleSellers.cs new file mode 100644 index 00000000..2125848c --- /dev/null +++ b/test/BraunauMobil.VeloBasar.IntegrationTests/Steps/FirstBasar/SettleSellers.cs @@ -0,0 +1,46 @@ +namespace BraunauMobil.VeloBasar.IntegrationTests.Steps.FirstBasar; + +public class SettleSellers(TestContext context) +{ + public async Task Run() + { + IHtmlDocument sellerList = await context.HttpClient.NavigateMenuAsync("Seller"); + sellerList.Title.Should().Be("Seller - Velo Basar"); + IHtmlAnchorElement detailsLink = sellerList.QueryTableDetailsLink(ID.Sellers.SchattenfellMagsame); + + IHtmlDocument sellerDetails = await context.HttpClient.GetDocumentAsync(detailsLink.Href); + sellerDetails.Title.Should().Be("Seller #1 Schattenfell Magsame - Velo Basar"); + IHtmlAnchorElement settleLink = sellerDetails.QueryAnchorByText("Settle"); + + IHtmlDocument successDocument = await context.HttpClient.GetDocumentAsync(settleLink.Href); + successDocument.Title.Should().Be("Settlement #1 - Velo Basar"); + + IHtmlAnchorElement voucherAnchor = successDocument.QueryAnchorByText("Voucher"); + SettlementDocumentModel document = await context.HttpClient.GetSettlementDocumentAsync(voucherAnchor.Href); + document.Should().BeEquivalentTo(context.SettlementDocument("XYZ - First Bazaar : Settlement #1", + "Edoras, 5/4/2063", + "Schattenfell Magsame".Line("Heidenschuss 41").Line("6295 Hobbingen").Line(), + "Seller.-ID: 1", + "Sales commission (10.00% of $92.99):", + "$92.99", + "$9.30", + "$83.69", + "1 Product", + "$92.99", + [ + new ProductTableRowDocumentModel("1", "Votec - Children's bike".Line("Votec VRC Comp").Line(" green 1067425379"), "24", "$92.99", null) + ], + "1 Product", + "$98.89", + [ + new ProductTableRowDocumentModel("2", "KTM - Steel steed".Line("Steed 1").Line(" red 1239209209"), "22", "$98.89", null) + ], + false, + "Schattenfell Magsame", + "", + context.BankingQrCode("Schattenfell Magsame", "EUR83.69", "XYZ - Revenue First Bazaar"), + "Edoras on Thursday, April 5, 2063 at 11:22 AM" + ) + ); + } +} diff --git a/test/BraunauMobil.VeloBasar.IntegrationTests/Steps/InitialSetup.cs b/test/BraunauMobil.VeloBasar.IntegrationTests/Steps/InitialSetup.cs new file mode 100644 index 00000000..a6bea8f1 --- /dev/null +++ b/test/BraunauMobil.VeloBasar.IntegrationTests/Steps/InitialSetup.cs @@ -0,0 +1,21 @@ +namespace BraunauMobil.VeloBasar.IntegrationTests.Steps; + +public class InitialSetup(TestContext context) +{ + public async Task Run() + { + IHtmlDocument intialSetupDocument = await context.HttpClient.GetDocumentAsync(""); + intialSetupDocument.Title.Should().Be("Initial setup - Velo Basar"); + IHtmlFormElement form = intialSetupDocument.QueryForm(); + IHtmlButtonElement submitButton = intialSetupDocument.QueryElement("button"); + + IHtmlDocument postDocument = await context.HttpClient.SendFormAsync(form, submitButton, new Dictionary + { + { "AdminUserEMail", "admin@valar.me" }, + { "GenerateCountries", true }, + { "GenerateProductTypes", true }, + { "GenerateZipCodes", true }, + }); + postDocument.Title.Should().Be("Log in - Velo Basar"); + } +} diff --git a/test/BraunauMobil.VeloBasar.IntegrationTests/TestContext.cs b/test/BraunauMobil.VeloBasar.IntegrationTests/TestContext.cs new file mode 100644 index 00000000..99615190 --- /dev/null +++ b/test/BraunauMobil.VeloBasar.IntegrationTests/TestContext.cs @@ -0,0 +1,138 @@ +using BraunauMobil.VeloBasar.Configuration; +using BraunauMobil.VeloBasar.Models.Documents; + +namespace BraunauMobil.VeloBasar.IntegrationTests; + +public record TestContext(IServiceProvider ServiceProvider, HttpClient HttpClient, PrintSettings PrintSettings) +{ + public AcceptanceDocumentModel AcceptanceDocument(string title, string locationAndDateText, string addressText, string sellerIdText, string statusLink, string sellerToken, string signatureText, string productTableCountText, string productTablePriceText, IReadOnlyCollection productTableRows) + => new(title, + locationAndDateText, + "Page {0} of {1}", + " - powered by https://github.com/braunau-mobil/velo-basar", + PrintSettings.PageMargins, + PrintSettings.Acceptance.SubTitle, + addressText, + sellerIdText, + true, + statusLink, + PrintSettings.Acceptance.TokenTitle, + sellerToken, + "For XYZ: ______________________________", + signatureText, + PrintSettings.QrCodeLengthMillimeters, + new ProductsTableDocumentModel( + "Id", + "Procuct description", + "Size", + "Price", + "Sum:", + productTableCountText, + productTablePriceText, + null, + productTableRows + ) + ); + + public string BankingQrCode(string seller, string amount, string decscription) + { + ArgumentNullException.ThrowIfNull(seller); + ArgumentNullException.ThrowIfNull(amount); + ArgumentNullException.ThrowIfNull(decscription); + + return "BCD" + .Line("002") + .Line("1") + .Line("SCT") + .Line() + .Line(seller) + .Line() + .Line(amount) + .Line() + .Line() + .Line(decscription) + .Line() + .Line(); + } + + public SaleDocumentModel SaleDocument(string title, string locationAndDateText, string productTableCountText, string productTablePriceText, IReadOnlyCollection productTableRows) + => new(title, + locationAndDateText, + "Page {0} of {1}", + " - powered by https://github.com/braunau-mobil/velo-basar", + PrintSettings.PageMargins, + PrintSettings.Sale.SubTitle, + true, + PrintSettings.BannerFilePath!, + PrintSettings.BannerSubtitle, + PrintSettings.Website, + PrintSettings.Sale.HintText, + PrintSettings.Sale.FooterText, + new ProductsTableDocumentModel( + "Id", + "Procuct description", + "Size", + "Price", + "Sum:", + productTableCountText, + productTablePriceText, + PrintSettings.Sale.SellerInfoText, + productTableRows + ) + ); + + public SettlementDocumentModel SettlementDocument(string title, string locationAndDateText, string addressText, string sellerIdText, string commissionPartText, string payoutAmountInclComissionText, string payoutCommissionAmountText, string payoutAmountText, string payoutTableCountText, string payoutTablePriceText, IReadOnlyCollection payoutTableRows, string pickupTableCountText, string pickupTablePriceText, IReadOnlyCollection pickupTableRows, bool addBankingQrCode, string bankAccountHolder, string iban, string bankingQrCodeContent, string signatureText) + => new(title, + locationAndDateText, + "Page {0} of {1}", + " - powered by https://github.com/braunau-mobil/velo-basar", + PrintSettings.PageMargins, + true, + PrintSettings.BannerFilePath!, + PrintSettings.BannerSubtitle, + PrintSettings.Website, + addressText, + sellerIdText, + new SettlementCommisionSummaryModel( + "Revenue from items sold:", + "Costs:", + "Total amount:", + commissionPartText, + payoutAmountInclComissionText, + payoutCommissionAmountText, + payoutAmountText + ), + new ProductsTableDocumentModel( + "Id", + "Procuct description", + "Size", + "Selling price", + "Sum:", + payoutTableCountText, + payoutTablePriceText, + null, + payoutTableRows + ), + PrintSettings.Settlement.SoldTitle, + new ProductsTableDocumentModel( + "Id", + "Procuct description", + "Size", + "Price", + "Sum:", + pickupTableCountText, + pickupTablePriceText, + null, + pickupTableRows + ), + PrintSettings.Settlement.NotSoldTitle, + PrintSettings.Settlement.ConfirmationText, + addBankingQrCode, + PrintSettings.QrCodeLengthMillimeters, + bankAccountHolder, + iban, + bankingQrCodeContent, + "Signature ______________________________", + signatureText + ); +} diff --git a/test/BraunauMobil.VeloBasar.IntegrationTests/VeloBasarTest.cs b/test/BraunauMobil.VeloBasar.IntegrationTests/VeloBasarTest.cs new file mode 100644 index 00000000..db26301e --- /dev/null +++ b/test/BraunauMobil.VeloBasar.IntegrationTests/VeloBasarTest.cs @@ -0,0 +1,46 @@ +using BraunauMobil.VeloBasar.Configuration; +using Microsoft.Extensions.Options; + +namespace BraunauMobil.VeloBasar.IntegrationTests; + +public class VeloBasarTest + : IClassFixture +{ + private readonly TestContext _context; + + public VeloBasarTest(CustomWebApplicationFactory factory) + { + ArgumentNullException.ThrowIfNull(factory); + + IOptions printSettingsOption = factory.Services.GetRequiredService>(); + + _context = new TestContext(factory.Services, factory.CreateClient(), printSettingsOption.Value); + } + + [Fact] + public async Task Run() + { + await new Steps.InitialSetup(_context).Run(); + await Login(); + await new Steps.FirstBasar.Creation(_context).Run(); + await new Steps.FirstBasar.AcceptSellers(_context).Run(); + await new Steps.FirstBasar.SellProducts(_context).Run(); + await new Steps.FirstBasar.SettleSellers(_context).Run(); + } + + private async Task Login() + { + IHtmlDocument loginDocument = await _context.HttpClient.GetDocumentAsync(""); + loginDocument.Title.Should().Be("Log in - Velo Basar"); + + IHtmlFormElement form = loginDocument.QueryForm(); + IHtmlButtonElement submitButton = loginDocument.QueryElement("button"); + + IHtmlDocument postDocument = await _context.HttpClient.SendFormAsync(form, submitButton, new Dictionary + { + { "Email", "admin@valar.me" }, + { "Password", "root" } + }); + postDocument.Title.Should().Be("Bazaars - Velo Basar"); + } +} diff --git a/test/BraunauMobil.VeloBasar.IntegrationTests/X.cs b/test/BraunauMobil.VeloBasar.IntegrationTests/X.cs new file mode 100644 index 00000000..a37bd52b --- /dev/null +++ b/test/BraunauMobil.VeloBasar.IntegrationTests/X.cs @@ -0,0 +1,18 @@ +using BraunauMobil.VeloBasar.IntegrationTests.Mockups; + +namespace BraunauMobil.VeloBasar.IntegrationTests; + +public static class X +{ + public static DateTime FirstContactDay { get; } = new DateTime(2063, 04, 05, 11, 22, 33); + + public static ClockMock Clock { get; } = new (FirstContactDay); + + public static string Line(this string s, string nextLine = "") + { + ArgumentNullException.ThrowIfNull(s); + ArgumentNullException.ThrowIfNull(nextLine); + + return s + Environment.NewLine + nextLine; + } +} diff --git a/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/Extensions.cs b/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/Extensions.cs deleted file mode 100644 index 22f187d2..00000000 --- a/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/Extensions.cs +++ /dev/null @@ -1,166 +0,0 @@ -using BraunauMobil.VeloBasar.Data; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using System.Reflection; - -namespace BraunauMobil.VeloBasar.Tests.IntegrationTests; - -public static class Extensions -{ - public static void AssertDb(this IServiceProvider services, Action what) - { - ArgumentNullException.ThrowIfNull(services); - ArgumentNullException.ThrowIfNull(what); - - using IServiceScope scope = services.CreateScope(); - VeloDbContext db = scope.ServiceProvider.GetRequiredService(); - using (new AssertionScope()) - { - what(db); - } - } - - public static TResult AssertDb(this IServiceProvider services, Func what) - { - ArgumentNullException.ThrowIfNull(services); - ArgumentNullException.ThrowIfNull(what); - - using IServiceScope scope = services.CreateScope(); - VeloDbContext db = scope.ServiceProvider.GetRequiredService(); - using (new AssertionScope()) - { - return what(db); - } - } - - public static ProductEntity AssertProduct(this VeloDbContext db, int productId) - { - ArgumentNullException.ThrowIfNull(db); - - return db.Products.AsNoTracking().Should().Contain(p => p.Id == productId).Subject; - } - - public static void AssertProductStorageState(this VeloDbContext db, int productId, StorageState expectedStorageState) - { - ArgumentNullException.ThrowIfNull(db); - - ProductEntity product = db.AssertProduct(productId); - product.StorageState.Should().Be(expectedStorageState); - } - - public static void AssertProductValueState(this VeloDbContext db, int productId, ValueState expectedValueState) - { - ArgumentNullException.ThrowIfNull(db); - - ProductEntity product = db.AssertProduct(productId); - product.ValueState.Should().Be(expectedValueState); - } - - public static TransactionEntity AssertTransaction(this VeloDbContext db, int basarId, TransactionType type, int number) - { - ArgumentNullException.ThrowIfNull(db); - - return db.Transactions - .Include(t => t.Seller) - .Include(t => t.Products) - .ThenInclude(pt => pt.Product) - .AsNoTracking() - .Should().Contain(t => t.BasarId == basarId && t.Type == type && t.Number == number).Subject; - } - - public static void ShouldBeLikeInserted(this ProductEntity product, AcceptProductModel acceptModel) - { - ArgumentNullException.ThrowIfNull(product); - ArgumentNullException.ThrowIfNull(acceptModel); - - product.Brand.Should().Be(acceptModel.Entity.Brand); - product.Color.Should().Be(acceptModel.Entity.Color); - product.FrameNumber.Should().Be(acceptModel.Entity.FrameNumber); - product.Description.Should().Be(acceptModel.Entity.Description); - product.Price.Should().Be(acceptModel.Entity.Price); - product.TireSize.Should().Be(acceptModel.Entity.TireSize); - product.TypeId.Should().Be(acceptModel.Entity.TypeId); - } - - public static void Do(this IServiceProvider services, Action what) - where TController : Controller - { - ArgumentNullException.ThrowIfNull(services); - ArgumentNullException.ThrowIfNull(what); - - using IServiceScope scope = services.CreateScope(); - TController controller = scope.CreateController(); - - what(controller); - } - - public static async Task Do(this IServiceProvider services, Func what) - where TController : Controller - { - ArgumentNullException.ThrowIfNull(services); - ArgumentNullException.ThrowIfNull(what); - - using IServiceScope scope = services.CreateScope(); - TController controller = scope.CreateController(); - - await what(controller); - } - - public static TResult Do(this IServiceProvider services, Func what) - where TController : Controller - { - ArgumentNullException.ThrowIfNull(services); - ArgumentNullException.ThrowIfNull(what); - - using IServiceScope scope = services.CreateScope(); - TController controller = scope.CreateController(); - - return what(controller); - } - - public static async Task Do(this IServiceProvider services, Func> what) - where TController : Controller - { - ArgumentNullException.ThrowIfNull(services); - ArgumentNullException.ThrowIfNull(what); - - using IServiceScope scope = services.CreateScope(); - TController controller = scope.CreateController(); - - return await what(controller); - } - - public static TController CreateController(this IServiceScope scope) - where TController : Controller - { - ArgumentNullException.ThrowIfNull(scope); - - CookiesMock cookies = scope.ServiceProvider.GetRequiredService(); - - HttpContext httpContext = new DefaultHttpContext() - { - RequestServices = scope.ServiceProvider, - }; - httpContext.Features.Set(new RequestCookiesFeature(cookies)); - httpContext.Features.Set(new ResponseCookiesFeatureWrapper(cookies)); - - IHttpContextAccessor httpContextAccessor = scope.ServiceProvider.GetRequiredService(); - httpContextAccessor.HttpContext = httpContext; - - IControllerFactory controllerFactory = scope.ServiceProvider.GetRequiredService(); - object controller = controllerFactory.CreateController(new ControllerContext - { - ActionDescriptor = new ControllerActionDescriptor - { - ControllerTypeInfo = typeof(TController).GetTypeInfo() - }, - HttpContext = httpContext - }); - - return (TController)controller; - } -} diff --git a/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/MainRun.cs b/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/MainRun.cs deleted file mode 100644 index 0a9c237a..00000000 --- a/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/MainRun.cs +++ /dev/null @@ -1,27 +0,0 @@ -using BraunauMobil.VeloBasar.Tests.IntegrationTests.MainRunSteps; - -namespace BraunauMobil.VeloBasar.Tests.IntegrationTests; - -public sealed class MainRun - : IDisposable -{ - private readonly TestContext _context = new (); - - public void Dispose() - { - _context.Dispose(); - GC.SuppressFinalize(this); - } - - [Fact] - public async Task Run() - { - _context.Clock.Now = new DateTimeOffset(2063, 04, 05, 11, 22, 33, TimeSpan.Zero); - - await new InitalSetup(_context).Run(); - await new BasarCreation(_context).Run(); - await new AcceptSellers(_context).Run(); - await new SellProducts(_context).Run(); - await new SettleSellers(_context).Run(); - } -} diff --git a/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/MainRunSteps/AcceptSellers.cs b/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/MainRunSteps/AcceptSellers.cs deleted file mode 100644 index ff8aa15a..00000000 --- a/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/MainRunSteps/AcceptSellers.cs +++ /dev/null @@ -1,196 +0,0 @@ -using BraunauMobil.VeloBasar.Controllers; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; - -namespace BraunauMobil.VeloBasar.Tests.IntegrationTests.MainRunSteps; - -public class AcceptSellers - : TestStepBase -{ - public AcceptSellers(TestContext testContext) - : base(testContext) - { } - - public override async Task Run() - { - // Create seller - Do(controller => - { - IActionResult result = controller.Start(V.FirstBasar.Id); - - RedirectResult redirect = result.Should().BeOfType().Subject; - redirect.Url.Should().Be("//action=CreateForAcceptance&controller=Seller"); - }); - - SellerCreateForAcceptanceModel createModel = await Do(async controller => - { - int? sellerId = null; - IActionResult result = await controller.CreateForAcceptance(sellerId); - - ViewResult view = result.Should().BeOfType().Subject; - view.ViewName.Should().BeNull(); - view.ViewData.ModelState.IsValid.Should().BeTrue(); - return view.Model.Should().BeOfType().Subject; - }); - - createModel.Seller.FirstName = "Frodo"; - createModel.Seller.LastName = "Beutlin"; - createModel.Seller.CountryId = V.Countries.Austria.Id; - createModel.Seller.ZIP = "5280"; - createModel.Seller.City = "Braunau"; - createModel.Seller.Street = "Am Stadtplatz 1"; - createModel.Seller.PhoneNumber = "0664 1234567"; - createModel.Seller.EMail = "frodo.beutlin@shirenet.at"; - createModel.Seller.HasNewsletterPermission = true; - createModel.Seller.IBAN = "AT036616345714985792"; - createModel.Seller.BankAccountHolder = "Ing. Frodo Beutlin"; - - await Do(async controller => - { - IActionResult result = await controller.CreateForAcceptance(createModel.Seller); - - RedirectResult redirect = result.Should().BeOfType().Subject; - redirect.Url.Should().Be("//sellerId=1&action=StartForSeller&controller=AcceptSession"); - }); - - AssertDb(db => - { - SellerEntity frodo = db.Sellers.AsNoTracking().Should().Contain(s => s.FirstName == "Frodo" && s.LastName == "Beutlin").Subject; - V.Sellers.Frodo = frodo; - }); - - // Start accept session - await Do(async controller => - { - IActionResult result = await controller.StartForSeller(V.Sellers.Frodo.Id, V.FirstBasar.Id); - - RedirectResult redirect = result.Should().BeOfType().Subject; - redirect.Url.Should().Be("//sessionId=1&action=Create&controller=AcceptProduct"); - }); - - int acceptSessionId = 0; - AssertDb(db => - { - AcceptSessionEntity session = db.AcceptSessions.AsNoTracking().Should().Contain(s => s.SellerId == V.Sellers.Frodo.Id && s.BasarId == V.FirstBasar.Id).Subject; - acceptSessionId = session.Id; - }); - - await EnterProducts(acceptSessionId); - - // Finish accept session - await Do(async controller => - { - IActionResult result = await controller.Submit(acceptSessionId); - - RedirectResult redirect = result.Should().BeOfType().Subject; - redirect.Url.Should().Be("//id=1&action=Success&controller=Transaction"); - }); - - int acceptanceId = AssertDb(db => - { - TransactionEntity transaction = db.AssertTransaction(V.FirstBasar.Id, TransactionType.Acceptance, 1); - transaction.BasarId.Should().Be(V.FirstBasar.Id); - transaction.CanCancel.Should().BeFalse(); - transaction.CanHasDocument.Should().BeTrue(); - transaction.Change.Should().BeNull(); - transaction.ChildTransactions.Should().BeEmpty(); - transaction.DocumentId.Should().BeNull(); - transaction.NeedsBankingQrCodeOnDocument.Should().BeFalse(); - transaction.NeedsStatusPush.Should().BeTrue(); - transaction.Notes.Should().BeNull(); - transaction.Number.Should().Be(1); - transaction.ParentTransaction.Should().BeNull(); - transaction.TimeStamp.Should().Be(Context.Clock.GetCurrentDateTime()); - transaction.SellerId.Should().Be(V.Sellers.Frodo.Id); - transaction.Type.Should().Be(TransactionType.Acceptance); - transaction.UpdateDocumentOnDemand.Should().BeTrue(); - - transaction.Products.Should().HaveCount(2); - transaction.Products.Should().Contain(p => p.ProductId == V.Products.FirstBasar.Frodo.Stahlross.Id); - transaction.Products.Should().Contain(p => p.ProductId == V.Products.FirstBasar.Frodo.Einrad.Id); - - V.Products.FirstBasar.AssertStorageStates(db, StorageState.Available, StorageState.Available); - V.Products.FirstBasar.AssertValueStates(db, ValueState.NotSettled, ValueState.NotSettled); - - db.Files.AsNoTracking().Should().BeEmpty(); - - return transaction.Id; - }); - - - - await Do(async controller => - { - IActionResult result = await controller.Success(acceptanceId); - - ViewResult view = result.Should().BeOfType().Subject; - view.ViewName.Should().BeNull(); - view.ViewData.ModelState.IsValid.Should().BeTrue(); - TransactionSuccessModel model = view.Model.Should().BeOfType().Subject; - - model.AmountGiven.Should().Be(0); - model.Entity.Should().NotBeNull(); - model.Entity.Id.Should().Be(acceptanceId); - model.OpenDocument.Should().BeTrue(); - model.ShowAmountInput.Should().BeFalse(); - model.ShowChange.Should().BeFalse(); - }); - - AssertDb(db => - { - db.Files.AsNoTracking().Should().BeEmpty(); - }); - - // Download document - await Do(async controller => - { - IActionResult result = await controller.Document(acceptanceId); - - FileContentResult file = result.Should().BeOfType().Subject; - file.ContentType.Should().Be("application/pdf"); - file.FileDownloadName.Should().Be("2063-04-05T11:22:33_Acceptance-1.pdf"); - file.FileContents.Should().NotBeNull(); - }); - - AssertDb(db => - { - TransactionEntity acceptance = db.Transactions.AsNoTracking().Should().Contain(f => f.Id == acceptanceId).Subject; - - acceptance.DocumentId.Should().NotBeNull(); - }); - } - private async Task EnterProducts(int acceptSessionId) - { - V.Products.FirstBasar.Frodo.Stahlross = await EnterProduct(acceptSessionId, model => - { - model.CanAccept.Should().BeFalse(); - model.SellerId.Should().Be(V.Sellers.Frodo.Id); - model.SessionId.Should().Be(acceptSessionId); - model.Products.Should().BeEmpty(); - - model.Entity.TypeId = V.ProductTypes.SteelSteed.Id; - model.Entity.Brand = "Simplon"; - model.Entity.Color = "Schwarz"; - model.Entity.FrameNumber = "1234567890"; - model.Entity.Description = "Gepäckträger, Korb"; - model.Entity.TireSize = "26"; - model.Entity.Price = 120; - }); - - V.Products.FirstBasar.Frodo.Einrad = await EnterProduct(acceptSessionId, model => - { - model.CanAccept.Should().BeTrue(); - model.SellerId.Should().Be(V.Sellers.Frodo.Id); - model.SessionId.Should().Be(acceptSessionId); - model.Products.Should().HaveCount(1); - - model.Entity.TypeId = V.ProductTypes.Scooter.Id; - model.Entity.Brand = "AJATA"; - model.Entity.Color = "Rot"; - model.Entity.FrameNumber = "qe340t9ni-0i4"; - model.Entity.Description = ""; - model.Entity.TireSize = "22"; - model.Entity.Price = 40; - }); - } -} diff --git a/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/MainRunSteps/BasarCreation.cs b/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/MainRunSteps/BasarCreation.cs deleted file mode 100644 index e8bea743..00000000 --- a/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/MainRunSteps/BasarCreation.cs +++ /dev/null @@ -1,116 +0,0 @@ -using BraunauMobil.VeloBasar.Controllers; -using Microsoft.AspNetCore.Mvc; -using Xan.AspNetCore.Mvc.Crud; - -namespace BraunauMobil.VeloBasar.Tests.IntegrationTests.MainRunSteps; - -public class BasarCreation - : TestStepBase -{ - private const string _basarName = "1. Fahrradbasar"; - private const string _basarLocation = "Braunau am Inn"; - private const int _productCommissionPercentage = 10; - private static readonly DateTime _basarDate = new (2063, 04, 05); - - public BasarCreation(TestContext testContext) - : base(testContext) - { - } - - public override async Task Run() - { - // Leave defaults - BasarEntity basar = await Do(async controller => - { - IActionResult result = await controller.Create(); - - ViewResult view = result.Should().BeOfType().Subject; - view.ViewName.Should().Be("CrudCreate"); - view.ViewData.ModelState.IsValid.Should().BeTrue(); - CrudModel crudModel = view.Model.Should().BeOfType>().Subject; - crudModel.Entity.Should().NotBeNull(); - return crudModel.Entity; - }); - - await Do(async controller => - { - IActionResult result = await controller.Create(basar); - - ViewResult view = result.Should().BeOfType().Subject; - view.ViewName.Should().Be("CrudCreate"); - view.ViewData.ModelState.IsValid.Should().BeFalse(); - view.ViewData.ModelState.Should().ContainKey(nameof(BasarEntity.Name)); - CrudModel crudModel = view.Model.Should().BeOfType>().Subject; - crudModel.Entity.Should().Be(basar); - }); - - AssertDb(db => - { - db.Basars.Should().BeEmpty(); - }); - - // Name is empty - basar.Name = ""; - basar.ProductCommissionPercentage = _productCommissionPercentage; - basar.Date = _basarDate; - basar.Location = _basarLocation; - - await Do(async controller => - { - IActionResult result = await controller.Create(basar); - - ViewResult view = result.Should().BeOfType().Subject; - view.ViewName.Should().Be("CrudCreate"); - view.ViewData.ModelState.IsValid.Should().BeFalse(); - view.ViewData.ModelState.Should().ContainKey(nameof(BasarEntity.Name)); - CrudModel crudModel = view.Model.Should().BeOfType>().Subject; - crudModel.Entity.Should().Be(basar); - }); - - AssertDb(db => - { - db.Basars.Should().BeEmpty(); - }); - - // ProductCommissionPercentage is out of range - basar.Name = _basarName; - basar.ProductCommissionPercentage = 1000; - - await Do(async controller => - { - IActionResult result = await controller.Create(basar); - - ViewResult view = result.Should().BeOfType().Subject; - view.ViewName.Should().Be("CrudCreate"); - view.ViewData.ModelState.IsValid.Should().BeFalse(); - view.ViewData.ModelState.Should().ContainKey(nameof(BasarEntity.ProductCommissionPercentage)); - CrudModel crudModel = view.Model.Should().BeOfType>().Subject; - crudModel.Entity.Should().Be(basar); - }); - - AssertDb(db => - { - db.Basars.Should().BeEmpty(); - }); - - // Valid basar - basar.ProductCommissionPercentage = _productCommissionPercentage; - - await Do(async controller => - { - IActionResult result = await controller.Create(basar); - - RedirectResult redirect = result.Should().BeOfType().Subject; - redirect.Url.Should().Be("//PageIndex=1&State=Enabled&action=List&controller=Basar"); - }); - - // Assert - AssertDb(db => - { - V.FirstBasar = db.Basars.Should().ContainSingle(basar => basar.Name == _basarName).Subject; - V.FirstBasar.ProductCommissionPercentage.Should().Be(_productCommissionPercentage); - V.FirstBasar.Date.Should().Be(_basarDate); - V.FirstBasar.Location.Should().Be(_basarLocation); - }); - } -} diff --git a/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/MainRunSteps/InitalSetup.cs b/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/MainRunSteps/InitalSetup.cs deleted file mode 100644 index 56e11e59..00000000 --- a/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/MainRunSteps/InitalSetup.cs +++ /dev/null @@ -1,96 +0,0 @@ -using BraunauMobil.VeloBasar.Controllers; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; - -namespace BraunauMobil.VeloBasar.Tests.IntegrationTests.MainRunSteps; - -public class InitalSetup - : TestStepBase -{ - private const string _adminUserEMail = "dev@shirenet.at"; - - public InitalSetup(TestContext testContext) - : base(testContext) - { } - - public override async Task Run() - { - InitializationConfiguration configuration = Do(controller => - { - IActionResult result = controller.InitialSetup(); - - ViewResult view = result.Should().BeOfType().Subject; - view.ViewName.Should().BeNull(); - return view.Model.Should().BeOfType().Subject; - }); - - // No Admin EMail - configuration.AdminUserEMail = ""; - - await Do(async controller => - { - IActionResult result = await controller.InitialSetupConfirmed(configuration); - - ViewResult view = result.Should().BeOfType().Subject; - view.ViewData.ModelState.IsValid.Should().BeFalse(); - view.ViewData.ModelState.Should().ContainKey(nameof(InitializationConfiguration.AdminUserEMail)); - view.Model.Should().Be(configuration); - }); - - AssertDb(db => - { - db.Users.Should().BeEmpty(); - db.Countries.Should().BeEmpty(); - db.ZipCodes.Should().BeEmpty(); - db.ProductTypes.Should().BeEmpty(); - }); - - // Invalid Generation config - configuration.AdminUserEMail = _adminUserEMail; - configuration.GenerateCountries = false; - configuration.GenerateProductTypes = true; - configuration.GenerateZipCodes = true; - - await Do(async controller => - { - IActionResult result = await controller.InitialSetupConfirmed(configuration); - - ViewResult view = result.Should().BeOfType().Subject; - view.ViewData.ModelState.IsValid.Should().BeFalse(); - view.ViewData.ModelState.Should().ContainKey(nameof(InitializationConfiguration.GenerateZipCodes)); - view.Model.Should().Be(configuration); - }); - - AssertDb(db => - { - db.Users.Should().BeEmpty(); - db.Countries.Should().BeEmpty(); - db.ZipCodes.Should().BeEmpty(); - db.ProductTypes.Should().BeEmpty(); - }); - - // Valid config - configuration.GenerateCountries = true; - - await Do(async controller => - { - IActionResult result = await controller.InitialSetupConfirmed(configuration); - - RedirectResult redirect = result.Should().BeOfType().Subject; - redirect.Url.Should().Be("//action=Index&controller=Home"); - }); - - AssertDb(db => - { - V.AdminUser = db.Users.Should().Contain(user => user.UserName == _adminUserEMail).Subject; - - V.Countries.Austria = db.Countries.AsNoTracking().Should().Contain(x => x.Name == "Österreich").Subject; - V.Countries.Germany = db.Countries.AsNoTracking().Should().Contain(x => x.Name == "Deutschland").Subject; - - db.ZipCodes.AsNoTracking().Should().Contain(zipCode => zipCode.Zip == "5280"); - - V.ProductTypes.Scooter = db.ProductTypes.AsNoTracking().Should().Contain(x => x.Name == "Scooter").Subject; - V.ProductTypes.SteelSteed = db.ProductTypes.AsNoTracking().Should().Contain(x => x.Name == "Steel steed").Subject; - }); - } -} diff --git a/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/MainRunSteps/SellProducts.cs b/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/MainRunSteps/SellProducts.cs deleted file mode 100644 index bc17d0e1..00000000 --- a/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/MainRunSteps/SellProducts.cs +++ /dev/null @@ -1,132 +0,0 @@ -using BraunauMobil.VeloBasar.Controllers; -using BraunauMobil.VeloBasar.Cookies; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; - -namespace BraunauMobil.VeloBasar.Tests.IntegrationTests.MainRunSteps; - -public class SellProducts - : TestStepBase -{ - public SellProducts(TestContext testContext) - : base(testContext) - { } - - public override async Task Run() - { - CartModel cart = await Do(async controller => - { - IActionResult result = await controller.Index(); - - ViewResult view = result.Should().BeOfType().Subject; - view.ViewName.Should().BeNull(); - view.ViewData.ModelState.IsValid.Should().BeTrue(); - return view.ViewData.Model.Should().BeOfType().Subject; - }); - - cart.BasarId.Should().Be(0); - cart.HasProducts.Should().BeFalse(); - cart.ProductId.Should().Be(0); - cart.Products.Should().BeEmpty(); - - cart.ProductId = V.Products.FirstBasar.Frodo.Stahlross.Id; - - await Do(async controller => - { - IActionResult result = await controller.Add(cart); - - ViewResult view = result.Should().BeOfType().Subject; - view.ViewName.Should().Be("Index"); - view.ViewData.ModelState.IsValid.Should().BeTrue(); - view.ViewData.Model.Should().Be(cart); - }); - - cart.BasarId.Should().Be(0); - cart.HasProducts.Should().BeTrue(); - cart.ProductId.Should().Be(0); - cart.Products.Should().HaveCount(1); - cart.Products.Should().Contain(p => p.Id == V.Products.FirstBasar.Frodo.Stahlross.Id); - - await Do(async controller => - { - IActionResult result = await controller.Checkout(V.FirstBasar.Id); - - RedirectResult redirect = result.Should().BeOfType().Subject; - redirect.Url.Should().Be("//id=2&action=Success&controller=Transaction"); - }); - - CartCookie cartCookie = new(Context.Services.GetRequiredService(), Context.Services.GetRequiredService()); - cartCookie.GetCart().Should().BeEmpty(); - - int saleId = AssertDb(db => - { - TransactionEntity transaction = db.AssertTransaction(V.FirstBasar.Id, TransactionType.Sale, 1); - transaction.BasarId.Should().Be(V.FirstBasar.Id); - transaction.CanCancel.Should().BeTrue(); - transaction.CanHasDocument.Should().BeTrue(); - transaction.Change.Should().BeNull(); - transaction.ChildTransactions.Should().BeEmpty(); - transaction.DocumentId.Should().BeNull(); - transaction.NeedsBankingQrCodeOnDocument.Should().BeFalse(); - transaction.NeedsStatusPush.Should().BeTrue(); - transaction.Notes.Should().BeNull(); - transaction.Number.Should().Be(1); - transaction.ParentTransaction.Should().BeNull(); - transaction.TimeStamp.Should().Be(Context.Clock.GetCurrentDateTime()); - transaction.SellerId.Should().BeNull(); - transaction.Type.Should().Be(TransactionType.Sale); - transaction.UpdateDocumentOnDemand.Should().BeFalse(); - - transaction.Products.Should().HaveCount(1); - transaction.Products.Should().Contain(p => p.ProductId == V.Products.FirstBasar.Frodo.Stahlross.Id); - - V.Products.FirstBasar.AssertStorageStates(db, StorageState.Available, StorageState.Sold); - V.Products.FirstBasar.AssertValueStates(db, ValueState.NotSettled, ValueState.NotSettled); - - db.Files.AsNoTracking().Should().HaveCount(1); - - return transaction.Id; - }); - - await Do(async controller => - { - IActionResult result = await controller.Success(saleId); - - ViewResult view = result.Should().BeOfType().Subject; - view.ViewName.Should().BeNull(); - view.ViewData.ModelState.IsValid.Should().BeTrue(); - TransactionSuccessModel model = view.Model.Should().BeOfType().Subject; - - model.AmountGiven.Should().Be(0); - model.Entity.Should().NotBeNull(); - model.Entity.Id.Should().Be(saleId); - model.OpenDocument.Should().BeTrue(); - model.ShowAmountInput.Should().BeTrue(); - model.ShowChange.Should().BeFalse(); - }); - - AssertDb(db => - { - db.Files.AsNoTracking().Should().HaveCount(1); - }); - - // Download document - await Do(async controller => - { - IActionResult result = await controller.Document(saleId); - - FileContentResult file = result.Should().BeOfType().Subject; - file.ContentType.Should().Be("application/pdf"); - file.FileDownloadName.Should().Be("2063-04-05T11:22:33_Sale-2.pdf"); - file.FileContents.Should().NotBeNull(); - }); - - AssertDb(db => - { - TransactionEntity acceptance = db.Transactions.AsNoTracking().Should().Contain(f => f.Id == saleId).Subject; - - acceptance.DocumentId.Should().NotBeNull(); - }); - } -} diff --git a/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/MainRunSteps/SettleSellers.cs b/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/MainRunSteps/SettleSellers.cs deleted file mode 100644 index 669126c2..00000000 --- a/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/MainRunSteps/SettleSellers.cs +++ /dev/null @@ -1,95 +0,0 @@ -using BraunauMobil.VeloBasar.Controllers; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; - -namespace BraunauMobil.VeloBasar.Tests.IntegrationTests.MainRunSteps; - -public class SettleSellers - : TestStepBase -{ - public SettleSellers(TestContext testContext) - : base(testContext) - { } - - public override async Task Run() - { - await Do(async controller => - { - IActionResult result = await controller.Settle(V.FirstBasar.Id, V.Sellers.Frodo.Id); - - RedirectResult redirect = result.Should().BeOfType().Subject; - redirect.Url.Should().Be("//id=3&action=Success&controller=Transaction"); - }); - - int transactionId = AssertDb(db => - { - TransactionEntity transaction = db.AssertTransaction(V.FirstBasar.Id, TransactionType.Settlement, 1); - transaction.BasarId.Should().Be(V.FirstBasar.Id); - transaction.CanCancel.Should().BeFalse(); - transaction.CanHasDocument.Should().BeTrue(); - transaction.Change.Should().BeNull(); - transaction.ChildTransactions.Should().BeEmpty(); - transaction.DocumentId.Should().BeNull(); - transaction.NeedsBankingQrCodeOnDocument.Should().BeTrue(); - transaction.NeedsStatusPush.Should().BeTrue(); - transaction.Notes.Should().BeNull(); - transaction.Number.Should().Be(1); - transaction.ParentTransaction.Should().BeNull(); - transaction.TimeStamp.Should().Be(Context.Clock.GetCurrentDateTime()); - transaction.SellerId.Should().Be(V.Sellers.Frodo.Id); - transaction.Type.Should().Be(TransactionType.Settlement); - transaction.UpdateDocumentOnDemand.Should().BeFalse(); - - transaction.Products.Should().HaveCount(2); - transaction.Products.Should().Contain(p => p.ProductId == V.Products.FirstBasar.Frodo.Stahlross.Id); - transaction.Products.Should().Contain(p => p.ProductId == V.Products.FirstBasar.Frodo.Einrad.Id); - - V.Products.FirstBasar.AssertStorageStates(db, StorageState.Available, StorageState.Sold); - V.Products.FirstBasar.AssertValueStates(db, ValueState.Settled, ValueState.Settled); - - db.Files.AsNoTracking().Should().HaveCount(2); - - return transaction.Id; - }); - - await Do(async controller => - { - IActionResult result = await controller.Success(transactionId); - - ViewResult view = result.Should().BeOfType().Subject; - view.ViewName.Should().BeNull(); - view.ViewData.ModelState.IsValid.Should().BeTrue(); - TransactionSuccessModel model = view.Model.Should().BeOfType().Subject; - - model.AmountGiven.Should().Be(0); - model.Entity.Should().NotBeNull(); - model.Entity.Id.Should().Be(transactionId); - model.OpenDocument.Should().BeTrue(); - model.ShowAmountInput.Should().BeFalse(); - model.ShowChange.Should().BeTrue(); - }); - - AssertDb(db => - { - db.Files.AsNoTracking().Should().HaveCount(2); - }); - - // Download document - await Do(async controller => - { - IActionResult result = await controller.Document(transactionId); - - FileContentResult file = result.Should().BeOfType().Subject; - file.ContentType.Should().Be("application/pdf"); - file.FileDownloadName.Should().Be("2063-04-05T11:22:33_Settlement-3.pdf"); - file.FileContents.Should().NotBeNull(); - }); - - AssertDb(db => - { - TransactionEntity acceptance = db.Transactions.AsNoTracking().Should().Contain(f => f.Id == transactionId).Subject; - - acceptance.DocumentId.Should().NotBeNull(); - }); - } -} diff --git a/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/TestContext.cs b/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/TestContext.cs deleted file mode 100644 index e75d9d90..00000000 --- a/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/TestContext.cs +++ /dev/null @@ -1,137 +0,0 @@ -using BraunauMobil.VeloBasar.BusinessLogic; -using BraunauMobil.VeloBasar.Controllers; -using BraunauMobil.VeloBasar.Cookies; -using BraunauMobil.VeloBasar.Data; -using BraunauMobil.VeloBasar.Filters; -using BraunauMobil.VeloBasar.Pdf; -using BraunauMobil.VeloBasar.Rendering; -using BraunauMobil.VeloBasar.Routing; -using FluentValidation; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Routing; -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using System.Globalization; -using Xan.AspNetCore; -using Xan.AspNetCore.Http; -using Xan.AspNetCore.Mvc; -using Xan.AspNetCore.Mvc.Filters; -using Xan.Extensions; -using Xan.Extensions.Tasks; - -namespace BraunauMobil.VeloBasar.Tests.IntegrationTests; - -public sealed class TestContext -{ - private readonly CultureInfo _initialCultureInfo = CultureInfo.CurrentCulture; - private const string _connectionString = "DataSource=:memory:"; - private readonly SqliteConnection _connection; - private readonly WebApplication _app; - - public TestContext() - { - WebApplicationBuilder builder = WebApplication.CreateBuilder(); - builder.Logging.AddConsole(); - - _connection = new SqliteConnection(_connectionString); - ConfigureServices(builder.Services, builder.Configuration); - - WebApplication app = builder.Build(); - app.UseDeveloperExceptionPage(); - app.UseHttpsRedirection(); - app.UseStaticFiles(); - app.UseCookiePolicy(); - app.UseAuthentication(); - app.UseRouting(); - app.UseAuthorization(); - app.MapControllerRoute( - name: "default", - pattern: "{controller}/{action}", - defaults: new { controller = MvcHelper.ControllerName(), action = nameof(HomeController.Index) } - ); - - using (IServiceScope scope = app.Services.CreateScope()) - { - ILogger logger = scope.ServiceProvider.GetRequiredService>(); - VeloTexts.CheckIfAllIsTranslated(logger); - - VeloDbContext db = scope.ServiceProvider.GetRequiredService(); - db.Database.EnsureCreated(); - } - - _app = app; - - CultureInfo.CurrentUICulture = new CultureInfo("en-US"); - } - - private void ConfigureServices(IServiceCollection services, ConfigurationManager configuration) - { - services - .AddDefaultIdentity() - .AddEntityFrameworkStores(); - - services.AddControllersWithViews(options => - { - options.Filters.Add(); - options.Filters.Add(); - }) - .AddApplicationPart(typeof(HomeController).Assembly) - .AddViewLocalization(options => - { - options.ResourcesPath = "Resources"; - - }); - - services - .AddDbContext(options => - { - _connection.Open(); - options.UseSqlite(_connection) - .EnableSensitiveDataLogging(); - }) - .AddHttpContextAccessor() - .AddHttpClient() - .AddScoped() - .AddSingleton() - .AddSingleton(CultureInfo.CurrentUICulture) - .AddSingleton(Clock) - .AddBusinessLogic() - .AddScoped() - .AddVeloCookies() - .AddVeloRendering() - .AddVeloRouting() - .AddPdf() - .AddScoped() - .AddScoped() - .AddValidatorsFromAssemblyContaining() - .AddHostedService() - .AddSingleton(Cookies); - - services.AddVeloOptions(configuration); - services.AddVeloCrud(); - - PageSizeCookie.Options.MaxAge = TimeSpan.FromDays(2); - - services - .ConfigureVeloCookies() - .ConfigureVeloIdentity() - ; - } - - public ClockMock Clock { get; } = new(); - - public CookiesMock Cookies { get; } = new (); - - public IServiceProvider Services { get => _app.Services; } - - public void Dispose() - { - CultureInfo.CurrentUICulture = _initialCultureInfo; - _connection.Dispose(); - GC.SuppressFinalize(this); - } -} diff --git a/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/TestStepBase.cs b/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/TestStepBase.cs deleted file mode 100644 index 8f56e930..00000000 --- a/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/TestStepBase.cs +++ /dev/null @@ -1,92 +0,0 @@ -using BraunauMobil.VeloBasar.Controllers; -using BraunauMobil.VeloBasar.Data; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; - -namespace BraunauMobil.VeloBasar.Tests.IntegrationTests; - -public abstract class TestStepBase -{ - public TestStepBase(TestContext testContext) - { - Context = testContext; - } - - protected TestContext Context { get; } - - public abstract Task Run(); - - public void AssertDb(Action what) - { - ArgumentNullException.ThrowIfNull(what); - - Context.Services.AssertDb(what); - } - - public TResult AssertDb(Func what) - { - ArgumentNullException.ThrowIfNull(what); - - return Context.Services.AssertDb(what); - } - - public async Task EnterProduct(int acceptSessionId, Action customize) - { - AcceptProductModel acceptProductModel = await Do(async controller => - { - IActionResult result = await controller.Create(acceptSessionId); - - ViewResult view = result.Should().BeOfType().Subject; - view.ViewName.Should().Be("CreateEdit"); - view.ViewData.ModelState.IsValid.Should().BeTrue(); - return view.Model.Should().BeOfType().Subject; - }); - - customize(acceptProductModel); - - await Do(async controller => - { - IActionResult result = await controller.Create(acceptProductModel.Entity); - - RedirectResult redirect = result.Should().BeOfType().Subject; - redirect.Url.Should().Be($"//sessionId={acceptSessionId}&action=Create&controller=AcceptProduct"); - }); - - return AssertDb(db => - { - return db.Products.AsNoTracking().Should().Contain(p => p.SessionId == acceptSessionId && p.Price == acceptProductModel.Entity.Price).Subject; - }); - } - - public void Do(Action what) - where TController : Controller - { - ArgumentNullException.ThrowIfNull(what); - - Context.Services.Do(what); - } - - public async Task Do(Func what) - where TController : Controller - { - ArgumentNullException.ThrowIfNull(what); - - await Context.Services.Do(what); - } - - public TResult Do(Func what) - where TController : Controller - { - ArgumentNullException.ThrowIfNull(what); - - return Context.Services.Do(what); - } - - public async Task Do(Func> what) - where TController : Controller - { - ArgumentNullException.ThrowIfNull(what); - - return await Context.Services.Do(what); - } -} diff --git a/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/V.cs b/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/V.cs deleted file mode 100644 index 3d62c150..00000000 --- a/test/BraunauMobil.VeloBasar.Tests/IntegrationTests/V.cs +++ /dev/null @@ -1,61 +0,0 @@ -using BraunauMobil.VeloBasar.Data; -using Microsoft.AspNetCore.Identity; - -namespace BraunauMobil.VeloBasar.Tests.IntegrationTests; - -#nullable disable -public static class V -{ - public static IdentityUser AdminUser { get; set; } - - public static BasarEntity FirstBasar { get; set; } - - public static class Countries - { - public static CountryEntity Austria { get; set; } - public static CountryEntity Germany { get; set; } - } - - public static class ProductTypes - { - public static ProductTypeEntity Scooter { get; set; } - - public static ProductTypeEntity SteelSteed { get; set; } - } - - public static class Sellers - { - public static SellerEntity Frodo { get; set; } - } - - public static class Products - { - public static class FirstBasar - { - public static class Frodo - { - public static ProductEntity Einrad { get; set; } - - public static ProductEntity Stahlross { get; set; } - } - - public static void AssertStorageStates(VeloDbContext db - , StorageState einrad - , StorageState stahlross - ) - { - db.AssertProductStorageState(Frodo.Einrad.Id, einrad); - db.AssertProductStorageState(Frodo.Stahlross.Id, stahlross); - } - - public static void AssertValueStates(VeloDbContext db - , ValueState einrad - , ValueState stahlross - ) - { - db.AssertProductValueState(Frodo.Einrad.Id, einrad); - db.AssertProductValueState(Frodo.Stahlross.Id, stahlross); - } - } - } -} diff --git a/test/BraunauMobil.VeloBasar.Tests/Mockups/CookiesMock.cs b/test/BraunauMobil.VeloBasar.Tests/Mockups/CookiesMock.cs deleted file mode 100644 index 2c983df9..00000000 --- a/test/BraunauMobil.VeloBasar.Tests/Mockups/CookiesMock.cs +++ /dev/null @@ -1,79 +0,0 @@ -using Microsoft.AspNetCore.Http; -using System.Collections; -using System.Diagnostics.CodeAnalysis; -using Xan.Extensions.Collections.Generic; - -namespace BraunauMobil.VeloBasar.Tests.Mockups; - -public class CookiesMock - : IResponseCookies - , IRequestCookieCollection -{ - private readonly Dictionary _values = new(); - - public string? this[string key] - { - get - { - ArgumentNullException.ThrowIfNull(key); - - if (_values.TryGetValue(key, out string? value)) - { - return value; - } - return null; - } - } - - public int Count => _values.Count; - - public ICollection Keys => _values.Keys; - - public void Append(string key, string value) - { - ArgumentNullException.ThrowIfNull(key); - ArgumentNullException.ThrowIfNull(value); - - _values.Set(key, value); - } - - public void Append(string key, string value, CookieOptions _) - => Append(key, value); - - public bool ContainsKey(string key) - { - ArgumentNullException.ThrowIfNull(key); - - return _values.ContainsKey(key); - } - - public void Delete(string key) - { - ArgumentNullException.ThrowIfNull(key); - - _values.Remove(key); - } - - public void Delete(string key, CookieOptions _) - => Delete(key); - - public IEnumerator> GetEnumerator() - => _values.GetEnumerator(); - - public bool TryGetValue(string key, [NotNullWhen(true)] out string? value) - { - ArgumentNullException.ThrowIfNull(key); - - if (_values.TryGetValue(key, out string? foundValue)) - { - value = foundValue; - return true; - } - - value = null; - return false; - } - - IEnumerator IEnumerable.GetEnumerator() - => ((IEnumerable)_values).GetEnumerator(); -} diff --git a/test/BraunauMobil.VeloBasar.Tests/Mockups/ResponseCookiesFeatureWrapper.cs b/test/BraunauMobil.VeloBasar.Tests/Mockups/ResponseCookiesFeatureWrapper.cs deleted file mode 100644 index 9838fce2..00000000 --- a/test/BraunauMobil.VeloBasar.Tests/Mockups/ResponseCookiesFeatureWrapper.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; - -namespace BraunauMobil.VeloBasar.Tests.Mockups; - -public class ResponseCookiesFeatureWrapper - : IResponseCookiesFeature -{ - public ResponseCookiesFeatureWrapper(IResponseCookies cookies) - { - Cookies = cookies ?? throw new ArgumentNullException(nameof(cookies)); - } - - public IResponseCookies Cookies { get; } -}