diff --git a/backend/Directory.Packages.props b/backend/Directory.Packages.props index 022fa01..59c877f 100644 --- a/backend/Directory.Packages.props +++ b/backend/Directory.Packages.props @@ -13,8 +13,11 @@ + + + diff --git a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/RequestTypeServiceTests.cs b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/RequestTypeServiceTests.cs new file mode 100644 index 0000000..f6b9dc9 --- /dev/null +++ b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/RequestTypeServiceTests.cs @@ -0,0 +1,96 @@ +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Tnsu.ApprovalRoute.Application.RequestTypes; +using Tnsu.ApprovalRoute.IntegrationTests.Infrastructure; + +namespace Tnsu.ApprovalRoute.IntegrationTests.ApplicationServices; + +[CollectionDefinition(Name)] +public sealed class RequestTypeServiceCollection : ICollectionFixture +{ + public const string Name = nameof(RequestTypeServiceCollection); +} + +[Collection(RequestTypeServiceCollection.Name)] +public sealed class RequestTypeServiceTests : IDisposable +{ + private readonly ServiceProvider _services; + private readonly RequestTypeService _service; + + public RequestTypeServiceTests(PostgresDatabaseFixture fixture) + { + _services = TestServiceProviderFactory.Build(fixture.ConnectionString); + _service = _services.GetRequiredService(); + } + + public void Dispose() => _services.Dispose(); + + [Fact] + public async Task CreateAsync_Then_GetAsync_ReturnsCreatedEntity() + { + var created = await _service.CreateAsync(new CreateRequestTypeRequest("Vacation", "/vacation"), CancellationToken.None); + + created.IsSuccess.ShouldBeTrue(); + created.Value.Name.ShouldBe("Vacation"); + created.Value.PageUrl.ShouldBe("/vacation"); + + var fetched = await _service.GetAsync(created.Value.Id, CancellationToken.None); + + fetched.IsSuccess.ShouldBeTrue(); + fetched.Value.Id.ShouldBe(created.Value.Id); + fetched.Value.Name.ShouldBe("Vacation"); + } + + [Fact] + public async Task GetAsync_UnknownId_ReturnsFailedResult() + { + var result = await _service.GetAsync(Guid.NewGuid(), CancellationToken.None); + + result.IsFailed.ShouldBeTrue(); + } + + [Fact] + public async Task UpdateAsync_ChangesFields() + { + var created = await _service.CreateAsync(new CreateRequestTypeRequest("Old name", "/old"), CancellationToken.None); + + var updated = await _service.UpdateAsync( + created.Value.Id, new UpdateRequestTypeRequest("New name", "/new"), CancellationToken.None); + + updated.IsSuccess.ShouldBeTrue(); + updated.Value.Name.ShouldBe("New name"); + updated.Value.PageUrl.ShouldBe("/new"); + } + + [Fact] + public async Task UpdateAsync_UnknownId_ReturnsFailedResult() + { + var result = await _service.UpdateAsync( + Guid.NewGuid(), new UpdateRequestTypeRequest("X", "/x"), CancellationToken.None); + + result.IsFailed.ShouldBeTrue(); + } + + [Fact] + public async Task DeleteAsync_RemovesEntity() + { + var created = await _service.CreateAsync(new CreateRequestTypeRequest("Temp", "/temp"), CancellationToken.None); + + var deleted = await _service.DeleteAsync(created.Value.Id, CancellationToken.None); + deleted.IsSuccess.ShouldBeTrue(); + + var fetched = await _service.GetAsync(created.Value.Id, CancellationToken.None); + fetched.IsFailed.ShouldBeTrue(); + } + + [Fact] + public async Task ListAsync_IncludesCreatedEntity() + { + var created = await _service.CreateAsync(new CreateRequestTypeRequest("Listed type", "/listed"), CancellationToken.None); + + var list = await _service.ListAsync(CancellationToken.None); + + list.IsSuccess.ShouldBeTrue(); + list.Value.ShouldContain(x => x.Id == created.Value.Id); + } +} diff --git a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/TemplateRouteServiceTests.cs b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/TemplateRouteServiceTests.cs new file mode 100644 index 0000000..a982cae --- /dev/null +++ b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/TemplateRouteServiceTests.cs @@ -0,0 +1,161 @@ +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Tnsu.ApprovalRoute.Application.RequestTypes; +using Tnsu.ApprovalRoute.Application.TemplateRoutes; +using Tnsu.ApprovalRoute.Domain.Enums; +using Tnsu.ApprovalRoute.IntegrationTests.Infrastructure; + +namespace Tnsu.ApprovalRoute.IntegrationTests.ApplicationServices; + +[CollectionDefinition(Name)] +public sealed class TemplateRouteServiceCollection : ICollectionFixture +{ + public const string Name = nameof(TemplateRouteServiceCollection); +} + +[Collection(TemplateRouteServiceCollection.Name)] +public sealed class TemplateRouteServiceTests : IDisposable +{ + private static readonly Guid CompanyId = Guid.Parse("00000000-0000-0000-0000-000000000002"); + private static readonly Guid DepartmentId = Guid.Parse("00000000-0000-0000-0000-000000000001"); + + private readonly ServiceProvider _services; + private readonly TemplateRouteService _service; + private readonly RequestTypeService _requestTypeService; + + public TemplateRouteServiceTests(PostgresDatabaseFixture fixture) + { + _services = TestServiceProviderFactory.Build(fixture.ConnectionString); + _service = _services.GetRequiredService(); + _requestTypeService = _services.GetRequiredService(); + } + + public void Dispose() => _services.Dispose(); + + private async Task CreateRequestTypeAsync(string name) => + (await _requestTypeService.CreateAsync(new CreateRequestTypeRequest(name, $"/{name}"), CancellationToken.None)).Value.Id; + + [Fact] + public async Task CreateAsync_ThenGetAsync_ReturnsCreatedEntity() + { + var requestTypeId = await CreateRequestTypeAsync("Vacation"); + + var created = await _service.CreateAsync( + new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: true), + CancellationToken.None); + + created.IsSuccess.ShouldBeTrue(); + created.Value.RequestTypeId.ShouldBe(requestTypeId); + + var fetched = await _service.GetAsync(created.Value.Id, CancellationToken.None); + fetched.IsSuccess.ShouldBeTrue(); + fetched.Value.CompanyId.ShouldBe(CompanyId); + } + + [Fact] + public async Task CreateAsync_UnknownRequestType_ReturnsFailedResult() + { + var result = await _service.CreateAsync( + new CreateTemplateRouteRequest(RouteType.Sequential, Guid.NewGuid(), DepartmentId, CompanyId, ByProject: false, RequiresControl: false), + CancellationToken.None); + + result.IsFailed.ShouldBeTrue(); + } + + [Fact] + public async Task CreateAsync_DuplicateRoute_ReturnsFailedResult() + { + var requestTypeId = await CreateRequestTypeAsync("Business trip"); + var request = new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false); + + var first = await _service.CreateAsync(request, CancellationToken.None); + first.IsSuccess.ShouldBeTrue(); + + var duplicate = await _service.CreateAsync(request, CancellationToken.None); + duplicate.IsFailed.ShouldBeTrue(); + } + + [Fact] + public async Task UpdateAsync_ChangesFields() + { + var requestTypeId = await CreateRequestTypeAsync("Sick leave"); + var created = await _service.CreateAsync( + new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false), + CancellationToken.None); + + var updated = await _service.UpdateAsync( + created.Value.Id, + new UpdateTemplateRouteRequest(RouteType.Parallel, DepartmentId, CompanyId, ByProject: false, RequiresControl: true), + CancellationToken.None); + + updated.IsSuccess.ShouldBeTrue(); + updated.Value.RouteType.ShouldBe(RouteType.Parallel); + updated.Value.RequiresControl.ShouldBeTrue(); + } + + [Fact] + public async Task DeleteAsync_RemovesEntity() + { + var requestTypeId = await CreateRequestTypeAsync("Remote work"); + var created = await _service.CreateAsync( + new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false), + CancellationToken.None); + + var deleted = await _service.DeleteAsync(created.Value.Id, CancellationToken.None); + deleted.IsSuccess.ShouldBeTrue(); + + var fetched = await _service.GetAsync(created.Value.Id, CancellationToken.None); + fetched.IsFailed.ShouldBeTrue(); + } + + [Fact] + public async Task ResolveAsync_ExactDepartmentMatch_ReturnsMatchingStepsForStatus() + { + var requestTypeId = await CreateRequestTypeAsync("Exact match request"); + var route = await _service.CreateAsync( + new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: true), + CancellationToken.None); + + var stepService = _services.GetRequiredService(); + await stepService.CreateAsync( + route.Value.Id, + new Application.TemplateRouteSteps.CreateTemplateRouteStepRequest( + 1, "approver@tnsu.kz", "Lead", ProjectRole.None, HeadType.Chief, RouteStepStatus.Approval, false), + CancellationToken.None); + + var resolved = await _service.ResolveAsync( + new ResolveTemplateRouteRequest(requestTypeId, "someone@tnsu.kz", null, RouteStepStatus.Approval), + CancellationToken.None); + + resolved.IsSuccess.ShouldBeTrue(); + resolved.Value.RouteId.ShouldBe(route.Value.Id); + resolved.Value.Steps.ShouldHaveSingleItem(); + resolved.Value.Steps[0].ExecutorEmail.ShouldBe("approver@tnsu.kz"); + } + + [Fact] + public async Task ResolveAsync_NoDepartmentRoute_FallsBackToCompanyWideRoute() + { + var requestTypeId = await CreateRequestTypeAsync("Company-wide request"); + var route = await _service.CreateAsync( + new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId: null, CompanyId, ByProject: false, RequiresControl: false), + CancellationToken.None); + + var resolved = await _service.ResolveAsync( + new ResolveTemplateRouteRequest(requestTypeId, "someone@tnsu.kz", null, RouteStepStatus.Execution), + CancellationToken.None); + + resolved.IsSuccess.ShouldBeTrue(); + resolved.Value.RouteId.ShouldBe(route.Value.Id); + } + + [Fact] + public async Task ResolveAsync_UnknownRequestType_ReturnsFailedResult() + { + var resolved = await _service.ResolveAsync( + new ResolveTemplateRouteRequest(Guid.NewGuid(), "someone@tnsu.kz", null, RouteStepStatus.Approval), + CancellationToken.None); + + resolved.IsFailed.ShouldBeTrue(); + } +} diff --git a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/TemplateRouteStepServiceTests.cs b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/TemplateRouteStepServiceTests.cs new file mode 100644 index 0000000..4b59a9b --- /dev/null +++ b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/TemplateRouteStepServiceTests.cs @@ -0,0 +1,154 @@ +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Tnsu.ApprovalRoute.Application.RequestTypes; +using Tnsu.ApprovalRoute.Application.TemplateRoutes; +using Tnsu.ApprovalRoute.Application.TemplateRouteSteps; +using Tnsu.ApprovalRoute.Domain.Enums; +using Tnsu.ApprovalRoute.IntegrationTests.Infrastructure; + +namespace Tnsu.ApprovalRoute.IntegrationTests.ApplicationServices; + +[CollectionDefinition(Name)] +public sealed class TemplateRouteStepServiceCollection : ICollectionFixture +{ + public const string Name = nameof(TemplateRouteStepServiceCollection); +} + +[Collection(TemplateRouteStepServiceCollection.Name)] +public sealed class TemplateRouteStepServiceTests : IDisposable +{ + private static readonly Guid CompanyId = Guid.Parse("00000000-0000-0000-0000-000000000002"); + + private readonly ServiceProvider _services; + private readonly TemplateRouteStepService _service; + private readonly TemplateRouteService _routeService; + private readonly RequestTypeService _requestTypeService; + + public TemplateRouteStepServiceTests(PostgresDatabaseFixture fixture) + { + _services = TestServiceProviderFactory.Build(fixture.ConnectionString); + _service = _services.GetRequiredService(); + _routeService = _services.GetRequiredService(); + _requestTypeService = _services.GetRequiredService(); + } + + public void Dispose() => _services.Dispose(); + + private async Task CreateRouteAsync(string requestTypeName, bool byProject) + { + var requestTypeId = (await _requestTypeService.CreateAsync( + new CreateRequestTypeRequest(requestTypeName, $"/{requestTypeName}"), CancellationToken.None)).Value.Id; + + var route = await _routeService.CreateAsync( + new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, null, CompanyId, byProject, RequiresControl: false), + CancellationToken.None); + + return route.Value.Id; + } + + [Fact] + public async Task CreateAsync_UnknownRoute_ReturnsFailedResult() + { + var result = await _service.CreateAsync( + Guid.NewGuid(), + new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false), + CancellationToken.None); + + result.IsFailed.ShouldBeTrue(); + } + + [Fact] + public async Task CreateAsync_ProjectRoleOnNonProjectRoute_ReturnsFailedResult() + { + var routeId = await CreateRouteAsync("Non project route", byProject: false); + + var result = await _service.CreateAsync( + routeId, + new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.Manager, HeadType.None, RouteStepStatus.Approval, false), + CancellationToken.None); + + result.IsFailed.ShouldBeTrue(); + } + + [Fact] + public async Task CreateAsync_ThenGetAsync_ReturnsCreatedStep() + { + var routeId = await CreateRouteAsync("Simple route", byProject: false); + + var created = await _service.CreateAsync( + routeId, + new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false), + CancellationToken.None); + + created.IsSuccess.ShouldBeTrue(); + + var fetched = await _service.GetAsync(routeId, created.Value.Id, CancellationToken.None); + fetched.IsSuccess.ShouldBeTrue(); + fetched.Value.ExecutorEmail.ShouldBe("a@b.kz"); + } + + [Fact] + public async Task CreateAsync_ByProjectRouteWithoutProjectRoleStep_ReturnsWarning() + { + var routeId = await CreateRouteAsync("By-project route without role", byProject: true); + + var created = await _service.CreateAsync( + routeId, + new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false), + CancellationToken.None); + + created.IsSuccess.ShouldBeTrue(); + created.Reasons.ShouldContain(r => r.Message.Contains("ролью в проекте")); + } + + [Fact] + public async Task CreateAsync_ByProjectRouteWithProjectRoleStep_HasNoWarning() + { + var routeId = await CreateRouteAsync("By-project route with role", byProject: true); + + var created = await _service.CreateAsync( + routeId, + new CreateTemplateRouteStepRequest(1, "", "", ProjectRole.Manager, HeadType.None, RouteStepStatus.Approval, false), + CancellationToken.None); + + created.IsSuccess.ShouldBeTrue(); + created.Reasons.ShouldBeEmpty(); + } + + [Fact] + public async Task UpdateAsync_ChangesFields() + { + var routeId = await CreateRouteAsync("Route to update step", byProject: false); + var created = await _service.CreateAsync( + routeId, + new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false), + CancellationToken.None); + + var updated = await _service.UpdateAsync( + routeId, + created.Value.Id, + new UpdateTemplateRouteStepRequest(2, "b@b.kz", "Senior Engineer", ProjectRole.None, HeadType.Chief, RouteStepStatus.Execution, true), + CancellationToken.None); + + updated.IsSuccess.ShouldBeTrue(); + updated.Value.ExecutorEmail.ShouldBe("b@b.kz"); + updated.Value.Status.ShouldBe(RouteStepStatus.Execution); + updated.Value.AllowReassignToOtherApprovers.ShouldBeTrue(); + } + + [Fact] + public async Task DeleteAsync_RemovesStep() + { + var routeId = await CreateRouteAsync("Route to delete step", byProject: false); + var created = await _service.CreateAsync( + routeId, + new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false), + CancellationToken.None); + + var deleted = await _service.DeleteAsync(routeId, created.Value.Id, CancellationToken.None); + deleted.IsSuccess.ShouldBeTrue(); + + var fetched = await _service.GetAsync(routeId, created.Value.Id, CancellationToken.None); + fetched.IsFailed.ShouldBeTrue(); + } +} diff --git a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/Infrastructure/PostgresDatabaseFixture.cs b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/Infrastructure/PostgresDatabaseFixture.cs new file mode 100644 index 0000000..cb76a16 --- /dev/null +++ b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/Infrastructure/PostgresDatabaseFixture.cs @@ -0,0 +1,59 @@ +using Microsoft.EntityFrameworkCore; +using Npgsql; +using Tnsu.ApprovalRoute.Infrastructure.Persistence; +using Xunit; + +namespace Tnsu.ApprovalRoute.IntegrationTests.Infrastructure; + +// Отдельная БД на один xUnit-collection (= один тестовый класс) в общем инстансе Postgres. +// Тесты внутри класса делят эту БД; разные классы (разные коллекции) получают разные БД. +public sealed class PostgresDatabaseFixture : IAsyncLifetime +{ + private readonly string _databaseName = $"tnsu_test_{Guid.NewGuid():N}"; + private readonly NpgsqlConnectionStringBuilder _adminConnectionBuilder = new(TestSettings.BaseConnectionString); + + public string ConnectionString { get; private set; } = string.Empty; + + public async Task InitializeAsync() + { + await using (var adminConnection = new NpgsqlConnection(_adminConnectionBuilder.ConnectionString)) + { + await adminConnection.OpenAsync(); + await using var createCommand = new NpgsqlCommand($"CREATE DATABASE \"{_databaseName}\"", adminConnection); + await createCommand.ExecuteNonQueryAsync(); + } + + ConnectionString = new NpgsqlConnectionStringBuilder(_adminConnectionBuilder.ConnectionString) + { + Database = _databaseName + }.ConnectionString; + + var options = new DbContextOptionsBuilder() + .UseNpgsql(ConnectionString) + .UseSnakeCaseNamingConvention() + .Options; + + await using var db = new ApprovalRouteDbContext(options); + await db.Database.MigrateAsync(); + } + + public async Task DisposeAsync() + { + await using var adminConnection = new NpgsqlConnection(_adminConnectionBuilder.ConnectionString); + await adminConnection.OpenAsync(); + + await using (var terminateCommand = new NpgsqlCommand( + """ + SELECT pg_terminate_backend(pid) FROM pg_stat_activity + WHERE datname = @databaseName AND pid <> pg_backend_pid(); + """, + adminConnection)) + { + terminateCommand.Parameters.AddWithValue("databaseName", _databaseName); + await terminateCommand.ExecuteNonQueryAsync(); + } + + await using var dropCommand = new NpgsqlCommand($"DROP DATABASE IF EXISTS \"{_databaseName}\"", adminConnection); + await dropCommand.ExecuteNonQueryAsync(); + } +} diff --git a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/Infrastructure/TestCurrentUser.cs b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/Infrastructure/TestCurrentUser.cs new file mode 100644 index 0000000..020d450 --- /dev/null +++ b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/Infrastructure/TestCurrentUser.cs @@ -0,0 +1,8 @@ +using Tnsu.ApprovalRoute.Application.Common.Interfaces; + +namespace Tnsu.ApprovalRoute.IntegrationTests.Infrastructure; + +internal sealed class TestCurrentUser(string? email = null) : ICurrentUser +{ + public string? Email { get; } = email; +} diff --git a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/Infrastructure/TestServiceProviderFactory.cs b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/Infrastructure/TestServiceProviderFactory.cs new file mode 100644 index 0000000..1290809 --- /dev/null +++ b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/Infrastructure/TestServiceProviderFactory.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Tnsu.ApprovalRoute.Application; +using Tnsu.ApprovalRoute.Application.Common.Interfaces; +using Tnsu.ApprovalRoute.Infrastructure; + +namespace Tnsu.ApprovalRoute.IntegrationTests.Infrastructure; + +// Собирает такой же DI-контейнер, как реальное приложение (те же extension-методы, что в Program.cs), +// но с тестовой БД и тестовым ICurrentUser вместо HttpContext-based реализации. +internal static class TestServiceProviderFactory +{ + public static ServiceProvider Build(string connectionString, string? currentUserEmail = "initiator@tnsu.kz") + { + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["ConnectionStrings:Postgres"] = connectionString + }) + .Build(); + + var services = new ServiceCollection(); + services.AddApprovalRouteInfrastructure(configuration); + services.AddApprovalRouteApplication(); + services.AddSingleton(new TestCurrentUser(currentUserEmail)); + + return services.BuildServiceProvider(); + } +} diff --git a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/Infrastructure/TestSettings.cs b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/Infrastructure/TestSettings.cs new file mode 100644 index 0000000..da4f2dc --- /dev/null +++ b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/Infrastructure/TestSettings.cs @@ -0,0 +1,8 @@ +namespace Tnsu.ApprovalRoute.IntegrationTests.Infrastructure; + +internal static class TestSettings +{ + public static string BaseConnectionString => + Environment.GetEnvironmentVariable("TEST_POSTGRES_CONNECTION") + ?? "Host=localhost;Port=5435;Database=tnsu_approval_route;Username=tnsu_approval_route;Password=tnsu_approval_route"; +} diff --git a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/RequestTypePostgresTests.cs b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/RequestTypePostgresTests.cs deleted file mode 100644 index 43f4131..0000000 --- a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/RequestTypePostgresTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Tnsu.ApprovalRoute.Domain.Entities; -using Tnsu.ApprovalRoute.Infrastructure.Persistence; - -namespace Tnsu.ApprovalRoute.IntegrationTests; - -public class RequestTypePostgresTests -{ - private static string ConnectionString => - Environment.GetEnvironmentVariable("TEST_POSTGRES_CONNECTION") - ?? "Host=localhost;Port=5435;Database=tnsu_approval_route;Username=tnsu_approval_route;Password=tnsu_approval_route"; - - [Fact] - public async Task CanInsertAndReadBack_RealPostgres() - { - var options = new DbContextOptionsBuilder() - .UseNpgsql(ConnectionString) - .UseSnakeCaseNamingConvention() - .Options; - - await using var db = new ApprovalRouteDbContext(options); - await db.Database.MigrateAsync(); - - // Транзакция без коммита — тест не оставляет данных в базе после себя. - await using var transaction = await db.Database.BeginTransactionAsync(); - - var entity = new RequestType { Name = "Integration test type", PageUrl = "/integration-test" }; - db.RequestTypes.Add(entity); - await db.SaveChangesAsync(); - - db.ChangeTracker.Clear(); - var loaded = await db.RequestTypes.AsNoTracking().FirstOrDefaultAsync(x => x.Id == entity.Id); - - Assert.NotNull(loaded); - Assert.Equal("Integration test type", loaded.Name); - Assert.Equal("/integration-test", loaded.PageUrl); - - await transaction.RollbackAsync(); - } -} diff --git a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/Tnsu.ApprovalRoute.IntegrationTests.csproj b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/Tnsu.ApprovalRoute.IntegrationTests.csproj index efe1f0d..1ee25ea 100644 --- a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/Tnsu.ApprovalRoute.IntegrationTests.csproj +++ b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/Tnsu.ApprovalRoute.IntegrationTests.csproj @@ -6,11 +6,15 @@ + + + +