diff --git a/AsbCloudWebApi.Tests/AsbCloudWebApi.Tests.csproj b/AsbCloudWebApi.Tests/AsbCloudWebApi.Tests.csproj index 03c795e6..5d42db1b 100644 --- a/AsbCloudWebApi.Tests/AsbCloudWebApi.Tests.csproj +++ b/AsbCloudWebApi.Tests/AsbCloudWebApi.Tests.csproj @@ -19,8 +19,10 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/AsbCloudWebApi.Tests/IntegrationTests/BaseIntegrationTest.cs b/AsbCloudWebApi.Tests/IntegrationTests/BaseIntegrationTest.cs new file mode 100644 index 00000000..3f047084 --- /dev/null +++ b/AsbCloudWebApi.Tests/IntegrationTests/BaseIntegrationTest.cs @@ -0,0 +1,47 @@ +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using AsbCloudApp.Data.User; +using AsbCloudDb; +using AsbCloudDb.Model; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace AsbCloudWebApi.Tests.IntegrationTests; + +public abstract class BaseIntegrationTest : IClassFixture +{ + private readonly IAsbCloudDbContext dbContext; + + protected readonly HttpClient httpClient; + protected readonly IServiceScope scope; + + protected BaseIntegrationTest(TestWebApplicationFactory factory) + { + scope = factory.Services.CreateScope(); + httpClient = factory.CreateClient(); + dbContext = scope.ServiceProvider.GetRequiredService(); + + dbContext.Database.EnsureCreatedAndMigrated(); + } + + protected void SetToken(string token) + { + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + } + + //TODO: поправить метод регистрации, сделать этот метод следующим образом: + //1. Пройти регистрацию + //2. Залогиниться под новым логином и паролем. Тестить всё под учёткой dev -- это неправильно, но пока так. + // Пока используется jwt токен dev, пока так захардкожено + protected Task RegisterUserAsync() + { + var authToken = new UserTokenDto + { + Token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiZGV2IiwiaWRDb21wYW55IjoiMSIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6InJvb3QiLCJuYmYiOjE3MDIzNjg3NzksImV4cCI6MTczMzkyNjM3OSwiaXNzIjoiYSIsImF1ZCI6ImEifQ.zMJHgoEkfifR28vIPxtABvznf8NFJjk33E9fxNZTwGM" + }; + + return Task.FromResult(authToken); + } +} \ No newline at end of file diff --git a/AsbCloudWebApi.Tests/IntegrationTests/Controllers/AdminDepositControllerTests.cs b/AsbCloudWebApi.Tests/IntegrationTests/Controllers/AdminDepositControllerTests.cs new file mode 100644 index 00000000..7d9d2f72 --- /dev/null +++ b/AsbCloudWebApi.Tests/IntegrationTests/Controllers/AdminDepositControllerTests.cs @@ -0,0 +1,24 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Services; + +namespace AsbCloudWebApi.Tests.IntegrationTests.Controllers; + +public class AdminDepositControllerTests : CrudControllerTests> +{ + public AdminDepositControllerTests(TestWebApplicationFactory factory) : base(factory) + { + } + + protected override string Uri => "api/admin/deposit"; + + protected override DepositDto GetFakerData() => new() + { + Caption = "Fake deposit", + Timezone = new SimpleTimezoneDto + { + Hours = 5, + IsOverride = false, + TimezoneId = "Екатеринбург" + } + }; +} \ No newline at end of file diff --git a/AsbCloudWebApi.Tests/IntegrationTests/Controllers/CrudControllerTests.cs b/AsbCloudWebApi.Tests/IntegrationTests/Controllers/CrudControllerTests.cs new file mode 100644 index 00000000..1f274a35 --- /dev/null +++ b/AsbCloudWebApi.Tests/IntegrationTests/Controllers/CrudControllerTests.cs @@ -0,0 +1,275 @@ +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Json; +using System.Threading.Tasks; +using AsbCloudApp.Data; +using AsbCloudApp.Services; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace AsbCloudWebApi.Tests.IntegrationTests.Controllers; + +public abstract class CrudControllerTests : BaseIntegrationTest + where T : class, IId + where TRepository : ICrudRepository +{ + private readonly TRepository repository; + + protected abstract string Uri { get; } + + protected abstract T GetFakerData(); + + protected CrudControllerTests(TestWebApplicationFactory factory) + : base(factory) + { + repository = scope.ServiceProvider.GetRequiredService(); + } + + [Fact] + public virtual async Task InsertAsync_Should_ReturnsSuccess() + { + //arrange + var fakerData = GetFakerData(); + + var request = new HttpRequestMessage + { + Content = JsonContent.Create(fakerData), + Method = HttpMethod.Post, + RequestUri = new Uri(Uri, UriKind.Relative) + }; + + var user = await RegisterUserAsync(); + SetToken(user.Token); + + //act + var response = await httpClient.SendAsync(request); + var result = await response.Content.ReadFromJsonAsync(); + + //assert + Assert.True(result > 0); + Assert.True(response.IsSuccessStatusCode); + } + + [Fact] + public virtual async Task InsertRangeAsync_Should_ReturnsSuccess() + { + //arrange + var fakerData = GetFakerData(); + + var request = new HttpRequestMessage + { + Content = JsonContent.Create(new[] { fakerData }), + Method = HttpMethod.Post, + RequestUri = new Uri($"{Uri}/range", UriKind.Relative) + }; + + var user = await RegisterUserAsync(); + SetToken(user.Token); + + //act + var response = await httpClient.SendAsync(request); + var result = await response.Content.ReadFromJsonAsync(); + + //assert + Assert.True(result > 0); + Assert.True(response.IsSuccessStatusCode); + } + + [Fact] + public virtual async Task InsertRangeAsync_Should_ReturnsBadRequest_WhenInputCollectionFakerDataIsEmpty() + { + //arrange + var request = new HttpRequestMessage + { + Content = JsonContent.Create(Enumerable.Empty()), + Method = HttpMethod.Post, + RequestUri = new Uri($"{Uri}/range", UriKind.Relative) + }; + + var user = await RegisterUserAsync(); + SetToken(user.Token); + + //act + var response = await httpClient.SendAsync(request); + + //assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + + [Fact] + public virtual async Task UpdateAsync_Should_ReturnsSuccess() + { + //arrange + var fakerData = GetFakerData(); + fakerData.Id = await repository.InsertAsync(fakerData, default); + + var request = new HttpRequestMessage + { + Content = JsonContent.Create(fakerData), + Method = HttpMethod.Put, + RequestUri = new Uri(Uri, UriKind.Relative) + }; + + var user = await RegisterUserAsync(); + SetToken(user.Token); + + //act + var response = await httpClient.SendAsync(request); + var result = await response.Content.ReadFromJsonAsync(); + + //assert + Assert.Equal(fakerData.Id, result); + Assert.True(response.IsSuccessStatusCode); + } + + [Fact] + public virtual async Task UpdateAsync_Should_ReturnsBadRequest_WhenFakerDataHasInvalidId() + { + //arrange + var fakerData = GetFakerData(); + + var request = new HttpRequestMessage + { + Content = JsonContent.Create(fakerData), + Method = HttpMethod.Put, + RequestUri = new Uri(Uri, UriKind.Relative) + }; + + var user = await RegisterUserAsync(); + SetToken(user.Token); + + //act + var response = await httpClient.SendAsync(request); + + //assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + + [Fact] + public virtual async Task DeleteAsync_Should_ReturnsSuccess() + { + //arrange + var fakerData = GetFakerData(); + fakerData.Id = await repository.InsertAsync(fakerData, default); + + var request = new HttpRequestMessage + { + Method = HttpMethod.Delete, + RequestUri = new Uri($"{Uri}/{fakerData.Id}", UriKind.Relative) + }; + + var user = await RegisterUserAsync(); + SetToken(user.Token); + + //act + var response = await httpClient.SendAsync(request); + var result = await response.Content.ReadFromJsonAsync(); + + //assert + Assert.True(result > 0); + Assert.True(response.IsSuccessStatusCode); + } + + [Fact] + public virtual async Task DeleteAsync_Should_ReturnsNoContent_WhenFakerDataHasInvalidId() + { + //arrange + const int fakerInvalidId = 0; + + var request = new HttpRequestMessage + { + Method = HttpMethod.Delete, + RequestUri = new Uri($"{Uri}/{fakerInvalidId}", UriKind.Relative) + }; + + var user = await RegisterUserAsync(); + SetToken(user.Token); + + //act + var response = await httpClient.SendAsync(request); + + //assert + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + } + + [Fact] + public virtual async Task GetOrDefaultAsync_Should_ReturnsSuccess() + { + //arrange + var fakerData = GetFakerData(); + fakerData.Id = await repository.InsertAsync(fakerData, default); + + var request = new HttpRequestMessage + { + Method = HttpMethod.Get, + RequestUri = new Uri($"{Uri}/{fakerData.Id}", UriKind.Relative) + }; + + var user = await RegisterUserAsync(); + SetToken(user.Token); + + //act + var response = await httpClient.SendAsync(request); + var result = await response.Content.ReadFromJsonAsync(); + + //assert + Assert.NotNull(result); + Assert.Equal(fakerData.Id, result.Id); + Assert.True(response.IsSuccessStatusCode); + } + + [Fact] + public virtual async Task GetOrDefaultAsync_Should_ReturnsNoContent_WhenFakerDataHasInvalidId() + { + //arrange + const int fakerInvalidId = 0; + + var request = new HttpRequestMessage + { + Method = HttpMethod.Get, + RequestUri = new Uri($"{Uri}/{fakerInvalidId}", UriKind.Relative) + }; + + var user = await RegisterUserAsync(); + SetToken(user.Token); + + //act + var response = await httpClient.SendAsync(request); + + //assert + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + } + + [Fact] + public virtual async Task GetAllAsync_Should_ReturnsSuccess() + { + //arrange + var existingFakerDataIds = (await repository.GetAllAsync(default)).Select(f => f.Id); + + foreach (var existingFakerDataId in existingFakerDataIds) + await repository.DeleteAsync(existingFakerDataId, default); + + var fakerData = GetFakerData(); + fakerData.Id = await repository.InsertAsync(fakerData, default); + + var request = new HttpRequestMessage + { + Method = HttpMethod.Get, + RequestUri = new Uri(Uri, UriKind.Relative) + }; + + var user = await RegisterUserAsync(); + SetToken(user.Token); + + //act + var response = await httpClient.SendAsync(request); + var result = await response.Content.ReadFromJsonAsync(); + + //assert + Assert.NotNull(result); + Assert.Single(result); + Assert.True(response.IsSuccessStatusCode); + } +} \ No newline at end of file diff --git a/AsbCloudWebApi.Tests/IntegrationTests/TestWebApplicationFactory.cs b/AsbCloudWebApi.Tests/IntegrationTests/TestWebApplicationFactory.cs new file mode 100644 index 00000000..0e645b8d --- /dev/null +++ b/AsbCloudWebApi.Tests/IntegrationTests/TestWebApplicationFactory.cs @@ -0,0 +1,46 @@ +using System.Linq; +using System.Threading.Tasks; +using AsbCloudDb.Model; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Testcontainers.PostgreSql; +using Xunit; + +namespace AsbCloudWebApi.Tests.IntegrationTests; + +public class TestWebApplicationFactory : WebApplicationFactory, + IAsyncLifetime +{ + private readonly PostgreSqlContainer _dbContainer = new PostgreSqlBuilder() + .WithImage("timescale/timescaledb:latest-pg15") + .WithDatabase("AsbCloud") + .WithUsername("postgres") + .WithPassword("postgres") + .Build(); + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.ConfigureServices(services => + { + var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions)); + + if (descriptor is not null) + services.Remove(descriptor); + + services.AddDbContext(options => + options.UseNpgsql(_dbContainer.GetConnectionString())); + }); + } + + public Task InitializeAsync() + { + return _dbContainer.StartAsync(); + } + + public new Task DisposeAsync() + { + return _dbContainer.StopAsync(); + } +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..45db8475 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +version: '3.9' + +services: + + postgres: + container_name: pg_db + image: timescale/timescaledb:latest-pg15 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: "AsbCloud" + volumes: + - ./timescaledb_data:/var/lib/postgresql/data + ports: + - "5434:5432" + + pgadmin: + container_name: pgadmin + image: dpage/pgadmin4 + environment: + PGADMIN_DEFAULT_EMAIL: noemail@noemail.com + PGADMIN_DEFAULT_PASSWORD: root + ports: + - "5050:80" \ No newline at end of file