forked from ddrilling/AsbCloudServer
Интеграционные тесты
1. Создание инфраструктуры для интегарционных тестов 2. Покрытие контроллера месторождений тестами
This commit is contained in:
parent
c0b15e1839
commit
88ce24b8bb
@ -15,6 +15,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsbCloudWebApi.Tests", "Asb
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SignalRTestClient", "SignalRTestClient\SignalRTestClient.csproj", "{E6B97963-4CEA-47B6-A0C8-625FFA9B7D69}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SignalRTestClient", "SignalRTestClient\SignalRTestClient.csproj", "{E6B97963-4CEA-47B6-A0C8-625FFA9B7D69}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsbCloudWebApi.IntegrationTests", "AsbCloudWebApi.IntegrationTests\AsbCloudWebApi.IntegrationTests.csproj", "{2A937DFD-8E78-4204-A6B9-F3195EAA5818}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -45,6 +47,10 @@ Global
|
|||||||
{E6B97963-4CEA-47B6-A0C8-625FFA9B7D69}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{E6B97963-4CEA-47B6-A0C8-625FFA9B7D69}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{E6B97963-4CEA-47B6-A0C8-625FFA9B7D69}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{E6B97963-4CEA-47B6-A0C8-625FFA9B7D69}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{E6B97963-4CEA-47B6-A0C8-625FFA9B7D69}.Release|Any CPU.Build.0 = Release|Any CPU
|
{E6B97963-4CEA-47B6-A0C8-625FFA9B7D69}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{2A937DFD-8E78-4204-A6B9-F3195EAA5818}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{2A937DFD-8E78-4204-A6B9-F3195EAA5818}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{2A937DFD-8E78-4204-A6B9-F3195EAA5818}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{2A937DFD-8E78-4204-A6B9-F3195EAA5818}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
50
AsbCloudWebApi.IntegrationTests/ApiTokenHelper.cs
Normal file
50
AsbCloudWebApi.IntegrationTests/ApiTokenHelper.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text;
|
||||||
|
using AsbCloudDb.Model;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
|
namespace AsbCloudWebApi.IntegrationTests;
|
||||||
|
|
||||||
|
public static class ApiTokenHelper
|
||||||
|
{
|
||||||
|
public static string GetAdminUserToken()
|
||||||
|
{
|
||||||
|
var user = new User()
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
IdCompany = 1,
|
||||||
|
Login = "test_user"
|
||||||
|
};
|
||||||
|
var roles = new[] { "root" };
|
||||||
|
|
||||||
|
return CreateToken(user, roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string CreateToken(User user, IEnumerable<string> roles)
|
||||||
|
{
|
||||||
|
var claims = new List<Claim>
|
||||||
|
{
|
||||||
|
new("id", user.Id.ToString()),
|
||||||
|
new(ClaimsIdentity.DefaultNameClaimType, user.Login),
|
||||||
|
new("idCompany", user.IdCompany.ToString()),
|
||||||
|
};
|
||||||
|
|
||||||
|
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
|
||||||
|
|
||||||
|
const string secret = "супер секретный ключ для шифрования";
|
||||||
|
|
||||||
|
var key = Encoding.ASCII.GetBytes(secret);
|
||||||
|
var tokenDescriptor = new SecurityTokenDescriptor
|
||||||
|
{
|
||||||
|
Issuer = "a",
|
||||||
|
Audience = "a",
|
||||||
|
Subject = new ClaimsIdentity(claims),
|
||||||
|
Expires = DateTime.UtcNow.AddHours(1),
|
||||||
|
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
|
||||||
|
};
|
||||||
|
var tokenHandler = new JwtSecurityTokenHandler();
|
||||||
|
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||||
|
return tokenHandler.WriteToken(token);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Bogus" Version="35.2.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.25" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||||
|
<PackageReference Include="Refit" Version="7.0.0" />
|
||||||
|
<PackageReference Include="xunit" Version="2.4.2" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\AsbCloudWebApi\AsbCloudWebApi.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
40
AsbCloudWebApi.IntegrationTests/BaseIntegrationTest.cs
Normal file
40
AsbCloudWebApi.IntegrationTests/BaseIntegrationTest.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text.Json;
|
||||||
|
using AsbCloudDb.Model;
|
||||||
|
using AsbCloudWebApi.IntegrationTests.Clients;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Refit;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AsbCloudWebApi.IntegrationTests;
|
||||||
|
|
||||||
|
public abstract class BaseIntegrationTest : IClassFixture<WebAppFactoryFixture>
|
||||||
|
{
|
||||||
|
private readonly IServiceScope scope;
|
||||||
|
private readonly HttpClient httpClient;
|
||||||
|
|
||||||
|
protected readonly IAsbCloudDbContext dbContext;
|
||||||
|
|
||||||
|
protected IAdminDepositClient adminDepositClient;
|
||||||
|
|
||||||
|
protected BaseIntegrationTest(WebAppFactoryFixture factory)
|
||||||
|
{
|
||||||
|
scope = factory.Services.CreateScope();
|
||||||
|
httpClient = factory.CreateClient();
|
||||||
|
|
||||||
|
dbContext = scope.ServiceProvider.GetRequiredService<IAsbCloudDbContext>();
|
||||||
|
|
||||||
|
var jwtToken = ApiTokenHelper.GetAdminUserToken();
|
||||||
|
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", jwtToken);
|
||||||
|
|
||||||
|
var jsonSerializerOptions = new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||||
|
PropertyNameCaseInsensitive = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var refitSettings = new RefitSettings(new SystemTextJsonContentSerializer(jsonSerializerOptions));
|
||||||
|
|
||||||
|
adminDepositClient = RestService.For<IAdminDepositClient>(httpClient, refitSettings);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
using AsbCloudApp.Data;
|
||||||
|
using Refit;
|
||||||
|
|
||||||
|
namespace AsbCloudWebApi.IntegrationTests.Clients;
|
||||||
|
|
||||||
|
public interface IAdminDepositClient
|
||||||
|
{
|
||||||
|
private const string BaseRoute = "/api/admin/deposit";
|
||||||
|
|
||||||
|
[Post(BaseRoute)]
|
||||||
|
Task<IApiResponse<int>> InsertAsync([Body] DepositBaseDto deposit);
|
||||||
|
|
||||||
|
[Post($"{BaseRoute}/range")]
|
||||||
|
Task<IApiResponse<int>> InsertRangeAsync([Body] IEnumerable<DepositBaseDto> deposits);
|
||||||
|
|
||||||
|
[Put($"{BaseRoute}")]
|
||||||
|
Task<IApiResponse<int>> UpdateAsync([Body] DepositBaseDto deposit);
|
||||||
|
|
||||||
|
[Get(BaseRoute + "/{id}")]
|
||||||
|
Task<IApiResponse<DepositBaseDto>> GetOrDefaultAsync(int id);
|
||||||
|
|
||||||
|
[Get(BaseRoute)]
|
||||||
|
Task<IApiResponse<IEnumerable<DepositBaseDto>>> GetAllAsync();
|
||||||
|
|
||||||
|
[Delete(BaseRoute + "/{id}")]
|
||||||
|
Task<IApiResponse<int>> DeleteAsync(int id);
|
||||||
|
}
|
@ -0,0 +1,186 @@
|
|||||||
|
using System.Net;
|
||||||
|
using AsbCloudApp.Data;
|
||||||
|
using AsbCloudWebApi.IntegrationTests.TestFakers;
|
||||||
|
using Mapster;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AsbCloudWebApi.IntegrationTests.Controllers;
|
||||||
|
|
||||||
|
public class AdminDepositControllerTests : BaseIntegrationTest
|
||||||
|
{
|
||||||
|
public AdminDepositControllerTests(WebAppFactoryFixture factory)
|
||||||
|
: base(factory)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InsertAsync_ReturnsSuccess_WhenNewItemIsValid()
|
||||||
|
{
|
||||||
|
//arrange
|
||||||
|
var expectedDto = DepositTestFaker.GetDeposit().Adapt<DepositBaseDto>();
|
||||||
|
|
||||||
|
//act
|
||||||
|
var response = await adminDepositClient.InsertAsync(expectedDto);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.True(response.Content > 0);
|
||||||
|
|
||||||
|
var entity = await dbContext.Deposits.FirstOrDefaultAsync(d => d.Id == response.Content);
|
||||||
|
var actualDto = entity?.Adapt<DepositBaseDto>();
|
||||||
|
|
||||||
|
var excludeProps = new[] { nameof(DepositBaseDto.Id) };
|
||||||
|
MatchHelper.Match(expectedDto, actualDto, excludeProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InsertRangeAsync_ReturnsSuccess_WhenAllNewItemsIsValid()
|
||||||
|
{
|
||||||
|
//arrange
|
||||||
|
var dto = DepositTestFaker.GetDeposit().Adapt<DepositBaseDto>();
|
||||||
|
|
||||||
|
//act
|
||||||
|
var responce = await adminDepositClient.InsertRangeAsync(new[] { dto });
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, responce.StatusCode);
|
||||||
|
Assert.Equal(1, responce.Content);
|
||||||
|
|
||||||
|
var entity = await dbContext.Deposits.OrderBy(d => d.Id).LastOrDefaultAsync();
|
||||||
|
var deposit = entity?.Adapt<DepositBaseDto>();
|
||||||
|
|
||||||
|
var excludeProps = new[] { nameof(DepositBaseDto.Id) };
|
||||||
|
MatchHelper.Match(dto, deposit, excludeProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateAsync_ReturnsBadRequest_WhenUpdatedItemHasInvalidId()
|
||||||
|
{
|
||||||
|
//arrange
|
||||||
|
var dto = DepositTestFaker.GetDeposit().Adapt<DepositBaseDto>();
|
||||||
|
|
||||||
|
//act
|
||||||
|
var response = await adminDepositClient.UpdateAsync(dto);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateAsync_ReturnsSuccess_WhenUpdatedItemIsValid()
|
||||||
|
{
|
||||||
|
//arrange
|
||||||
|
var dto = DepositTestFaker.GetDeposit().Adapt<DepositBaseDto>();
|
||||||
|
var insertResponse = await adminDepositClient.InsertAsync(dto);
|
||||||
|
|
||||||
|
dto.Id = insertResponse.Content;
|
||||||
|
dto.Caption = "Test";
|
||||||
|
dto.Latitude = 50;
|
||||||
|
dto.Longitude = 50;
|
||||||
|
dto.Timezone = new SimpleTimezoneDto
|
||||||
|
{
|
||||||
|
IsOverride = true,
|
||||||
|
Hours = 12
|
||||||
|
};
|
||||||
|
|
||||||
|
//act
|
||||||
|
var response = await adminDepositClient.UpdateAsync(dto);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.True(response.Content > 0);
|
||||||
|
|
||||||
|
var entity = await dbContext.Deposits.FirstOrDefaultAsync(d => d.Id == response.Content);
|
||||||
|
var deposit = entity?.Adapt<DepositBaseDto>();
|
||||||
|
|
||||||
|
MatchHelper.Match(dto, deposit);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetOrDefaultAsync_ReturnsSuccess_WhenIdIsValid()
|
||||||
|
{
|
||||||
|
//arrange
|
||||||
|
var dto = DepositTestFaker.GetDeposit().Adapt<DepositBaseDto>();
|
||||||
|
var insertResponse = await adminDepositClient.InsertAsync(dto);
|
||||||
|
var id = insertResponse.Content;
|
||||||
|
|
||||||
|
//act
|
||||||
|
var response = await adminDepositClient.GetOrDefaultAsync(id);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.NotNull(response.Content);
|
||||||
|
|
||||||
|
var entity = await dbContext.Deposits.FirstOrDefaultAsync(d => d.Id == response.Content.Id);
|
||||||
|
var deposit = entity?.Adapt<DepositBaseDto>();
|
||||||
|
|
||||||
|
var excludeProps = new[] { nameof(DepositBaseDto.Id) };
|
||||||
|
MatchHelper.Match(dto, deposit, excludeProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetOrDefaultAsync_ReturnsNoContent_WhenIdIsInvalid()
|
||||||
|
{
|
||||||
|
//arrange
|
||||||
|
const int id = 0;
|
||||||
|
|
||||||
|
//act
|
||||||
|
var responce = await adminDepositClient.GetOrDefaultAsync(id);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.Equal(HttpStatusCode.NoContent, responce.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetAllAsync_ReturnsSuccess()
|
||||||
|
{
|
||||||
|
//act
|
||||||
|
var response = await adminDepositClient.GetAllAsync();
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.NotNull(response.Content);
|
||||||
|
|
||||||
|
var expectedCount = await dbContext.Deposits.CountAsync();
|
||||||
|
Assert.Equal(expectedCount, response.Content.Count());
|
||||||
|
|
||||||
|
|
||||||
|
var entity = await dbContext.Deposits.FirstOrDefaultAsync();
|
||||||
|
var dto = entity?.Adapt<DepositBaseDto>();
|
||||||
|
|
||||||
|
MatchHelper.Match(dto, response.Content.FirstOrDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteAsync_ReturnsSuccess_WhenIdIsValid()
|
||||||
|
{
|
||||||
|
//arrange
|
||||||
|
var dto = DepositTestFaker.GetDeposit().Adapt<DepositBaseDto>();
|
||||||
|
var insertResponse = await adminDepositClient.InsertAsync(dto);
|
||||||
|
var id = insertResponse.Content;
|
||||||
|
|
||||||
|
//act
|
||||||
|
var response = await adminDepositClient.DeleteAsync(id);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.True(response.Content > 0);
|
||||||
|
|
||||||
|
var entity = await dbContext.Deposits.FirstOrDefaultAsync(d => d.Id == dto.Id);
|
||||||
|
Assert.Null(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteAsync_ReturnsNoContent_WhenIdIsInvalid()
|
||||||
|
{
|
||||||
|
//arrange
|
||||||
|
const int id = 0;
|
||||||
|
|
||||||
|
//act
|
||||||
|
var response = await adminDepositClient.DeleteAsync(id);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
31
AsbCloudWebApi.IntegrationTests/MatchHelper.cs
Normal file
31
AsbCloudWebApi.IntegrationTests/MatchHelper.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
|
||||||
|
namespace AsbCloudWebApi.IntegrationTests;
|
||||||
|
|
||||||
|
public static class MatchHelper
|
||||||
|
{
|
||||||
|
public static void Match<T>(T expected, T actual, IEnumerable<string>? excludeProps = null)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(expected, actual))
|
||||||
|
throw new EqualException(expected, actual);
|
||||||
|
|
||||||
|
if (expected is null || actual is null)
|
||||||
|
throw new EqualException(expected, actual);
|
||||||
|
|
||||||
|
var props = typeof(T).GetProperties(
|
||||||
|
BindingFlags.Public
|
||||||
|
| BindingFlags.Instance).Where(prop => prop.CanWrite);
|
||||||
|
|
||||||
|
if (excludeProps is not null && excludeProps.Any())
|
||||||
|
props = props.Where(prop => !excludeProps.Contains(prop.Name));
|
||||||
|
|
||||||
|
foreach (var prop in props)
|
||||||
|
{
|
||||||
|
var objValue = prop.GetValue(expected);
|
||||||
|
var anotherValue = prop.GetValue(actual);
|
||||||
|
if (objValue != null && !objValue.Equals(anotherValue))
|
||||||
|
throw new EqualException(expected, actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
using AsbCloudDb.Model;
|
||||||
|
using Bogus;
|
||||||
|
|
||||||
|
namespace AsbCloudWebApi.IntegrationTests.TestFakers;
|
||||||
|
|
||||||
|
public static class DepositTestFaker
|
||||||
|
{
|
||||||
|
public static Deposit GetDeposit() =>
|
||||||
|
GetDepositFaker().Generate();
|
||||||
|
|
||||||
|
public static IEnumerable<Deposit> GetDeposits(int count) =>
|
||||||
|
GetDepositFaker().Generate(count);
|
||||||
|
|
||||||
|
private static Faker<Deposit> GetDepositFaker() =>
|
||||||
|
new Faker<Deposit>()
|
||||||
|
.RuleFor(d => d.Id, 0)
|
||||||
|
.RuleFor(d => d.Caption, f => f.Random.String2(1, 50))
|
||||||
|
.RuleFor(d => d.Latitude, f => f.Random.Int(-90, 90))
|
||||||
|
.RuleFor(d => d.Longitude, f => f.Random.Int(-180, 180))
|
||||||
|
.RuleFor(d => d.Timezone, f => new SimpleTimezone
|
||||||
|
{
|
||||||
|
Hours = f.Random.Int(1, 12),
|
||||||
|
IsOverride = f.Random.Bool()
|
||||||
|
});
|
||||||
|
}
|
52
AsbCloudWebApi.IntegrationTests/WebAppFactoryFixture.cs
Normal file
52
AsbCloudWebApi.IntegrationTests/WebAppFactoryFixture.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
using AsbCloudDb;
|
||||||
|
using AsbCloudDb.Model;
|
||||||
|
using AsbCloudWebApi.IntegrationTests.TestFakers;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Testing;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AsbCloudWebApi.IntegrationTests;
|
||||||
|
|
||||||
|
public class WebAppFactoryFixture : WebApplicationFactory<Startup>,
|
||||||
|
IAsyncLifetime
|
||||||
|
{
|
||||||
|
private const string connectionString =
|
||||||
|
"Host=localhost;Database=test;Port=5433;Username=postgres;Password=root;Persist Security Info=True;Include Error Detail=True;";
|
||||||
|
|
||||||
|
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||||
|
{
|
||||||
|
builder.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
var descriptor = services.FirstOrDefault(d => d.ServiceType == typeof(DbContextOptions<AsbCloudDbContext>));
|
||||||
|
|
||||||
|
if (descriptor is not null)
|
||||||
|
services.Remove(descriptor);
|
||||||
|
|
||||||
|
services.AddDbContext<AsbCloudDbContext>(options =>
|
||||||
|
options.UseNpgsql(connectionString));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
using var scope = Services.CreateScope();
|
||||||
|
var scopedServices = scope.ServiceProvider;
|
||||||
|
var dbContext = scopedServices.GetRequiredService<AsbCloudDbContext>();
|
||||||
|
|
||||||
|
dbContext.Database.EnsureCreatedAndMigrated();
|
||||||
|
|
||||||
|
dbContext.Deposits.AddRange(DepositTestFaker.GetDeposits(15));
|
||||||
|
await dbContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public new async Task DisposeAsync()
|
||||||
|
{
|
||||||
|
using var scope = Services.CreateScope();
|
||||||
|
var scopedServices = scope.ServiceProvider;
|
||||||
|
var dbContext = scopedServices.GetRequiredService<AsbCloudDbContext>();
|
||||||
|
|
||||||
|
await dbContext.Database.EnsureDeletedAsync();
|
||||||
|
}
|
||||||
|
}
|
@ -22,7 +22,6 @@
|
|||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.25" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.25" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||||
<PackageReference Include="Testcontainers.PostgreSql" Version="3.6.0" />
|
|
||||||
<PackageReference Include="xunit" Version="2.4.2" />
|
<PackageReference Include="xunit" Version="2.4.2" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
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<TestWebApplicationFactory>
|
|
||||||
{
|
|
||||||
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<IAsbCloudDbContext>();
|
|
||||||
|
|
||||||
dbContext.Database.EnsureCreatedAndMigrated();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void SetToken(string token)
|
|
||||||
{
|
|
||||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: поправить метод регистрации, сделать этот метод следующим образом:
|
|
||||||
//1. Пройти регистрацию
|
|
||||||
//2. Залогиниться под новым логином и паролем. Тестить всё под учёткой dev -- это неправильно, но пока так.
|
|
||||||
// Пока используется jwt токен dev, пока так захардкожено
|
|
||||||
protected Task<UserTokenDto> RegisterUserAsync()
|
|
||||||
{
|
|
||||||
var authToken = new UserTokenDto
|
|
||||||
{
|
|
||||||
Token =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiZGV2IiwiaWRDb21wYW55IjoiMSIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6InJvb3QiLCJuYmYiOjE3MDIzNjg3NzksImV4cCI6MTczMzkyNjM3OSwiaXNzIjoiYSIsImF1ZCI6ImEifQ.zMJHgoEkfifR28vIPxtABvznf8NFJjk33E9fxNZTwGM"
|
|
||||||
};
|
|
||||||
|
|
||||||
return Task.FromResult(authToken);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
using AsbCloudApp.Data;
|
|
||||||
using AsbCloudApp.Services;
|
|
||||||
|
|
||||||
namespace AsbCloudWebApi.Tests.IntegrationTests.Controllers;
|
|
||||||
|
|
||||||
public class AdminDepositControllerTests : CrudControllerTests<DepositDto, ICrudRepository<DepositDto>>
|
|
||||||
{
|
|
||||||
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 = "Екатеринбург"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,275 +0,0 @@
|
|||||||
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<T, TRepository> : BaseIntegrationTest
|
|
||||||
where T : class, IId
|
|
||||||
where TRepository : ICrudRepository<T>
|
|
||||||
{
|
|
||||||
private readonly TRepository repository;
|
|
||||||
|
|
||||||
protected abstract string Uri { get; }
|
|
||||||
|
|
||||||
protected abstract T GetFakerData();
|
|
||||||
|
|
||||||
protected CrudControllerTests(TestWebApplicationFactory factory)
|
|
||||||
: base(factory)
|
|
||||||
{
|
|
||||||
repository = scope.ServiceProvider.GetRequiredService<TRepository>();
|
|
||||||
}
|
|
||||||
|
|
||||||
[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<int>();
|
|
||||||
|
|
||||||
//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<int>();
|
|
||||||
|
|
||||||
//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<T>()),
|
|
||||||
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<int>();
|
|
||||||
|
|
||||||
//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<int>();
|
|
||||||
|
|
||||||
//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<T>();
|
|
||||||
|
|
||||||
//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<T[]>();
|
|
||||||
|
|
||||||
//assert
|
|
||||||
Assert.NotNull(result);
|
|
||||||
Assert.Single(result);
|
|
||||||
Assert.True(response.IsSuccessStatusCode);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
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<Startup>,
|
|
||||||
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<AsbCloudDbContext>));
|
|
||||||
|
|
||||||
if (descriptor is not null)
|
|
||||||
services.Remove(descriptor);
|
|
||||||
|
|
||||||
services.AddDbContext<AsbCloudDbContext>(options =>
|
|
||||||
options.UseNpgsql(_dbContainer.GetConnectionString()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task InitializeAsync()
|
|
||||||
{
|
|
||||||
return _dbContainer.StartAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public new Task DisposeAsync()
|
|
||||||
{
|
|
||||||
return _dbContainer.StopAsync();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
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"
|
|
Loading…
Reference in New Issue
Block a user