Add integration tests
This commit is contained in:
@@ -14,7 +14,7 @@ public static class GetCars
|
|||||||
.WithTags("Cars");
|
.WithTags("Cars");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<IResult> Endpoint(
|
private static async Task<IResult> Endpoint(
|
||||||
ApplicationDbContext dbContext,
|
ApplicationDbContext dbContext,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|||||||
19
tests/WebApi.Tests.Integration/CarFaker.cs
Normal file
19
tests/WebApi.Tests.Integration/CarFaker.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using Bogus;
|
||||||
|
using Vegasco.WebApi.Cars;
|
||||||
|
|
||||||
|
namespace WebApi.Tests.Integration;
|
||||||
|
|
||||||
|
internal class CarFaker
|
||||||
|
{
|
||||||
|
private readonly Faker _faker = new();
|
||||||
|
|
||||||
|
internal CreateCar.Request CreateCarRequest()
|
||||||
|
{
|
||||||
|
return new CreateCar.Request(_faker.Vehicle.Model());
|
||||||
|
}
|
||||||
|
|
||||||
|
internal UpdateCar.Request UpdateCarRequest()
|
||||||
|
{
|
||||||
|
return new UpdateCar.Request(_faker.Vehicle.Model());
|
||||||
|
}
|
||||||
|
}
|
||||||
70
tests/WebApi.Tests.Integration/Cars/CreateCarTests.cs
Normal file
70
tests/WebApi.Tests.Integration/Cars/CreateCarTests.cs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using Vegasco.WebApi.Cars;
|
||||||
|
using Vegasco.WebApi.Persistence;
|
||||||
|
|
||||||
|
namespace WebApi.Tests.Integration.Cars;
|
||||||
|
|
||||||
|
[Collection(SharedTestCollection.Name)]
|
||||||
|
public class CreateCarTests : IAsyncLifetime
|
||||||
|
{
|
||||||
|
private readonly WebAppFactory _factory;
|
||||||
|
private readonly IServiceScope _scope;
|
||||||
|
private readonly ApplicationDbContext _dbContext;
|
||||||
|
|
||||||
|
private readonly CarFaker _carFaker = new();
|
||||||
|
|
||||||
|
public CreateCarTests(WebAppFactory factory)
|
||||||
|
{
|
||||||
|
_factory = factory;
|
||||||
|
_scope = _factory.Services.CreateScope();
|
||||||
|
_dbContext = _scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateCar_ShouldCreateCar_WhenRequestIsValid()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var createCarRequest = _carFaker.CreateCarRequest();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.Created);
|
||||||
|
var createdCar = await response.Content.ReadFromJsonAsync<CreateCar.Response>();
|
||||||
|
createdCar.Should().BeEquivalentTo(createCarRequest, o => o.ExcludingMissingMembers());
|
||||||
|
|
||||||
|
_dbContext.Cars.Should().ContainEquivalentOf(createdCar);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateCar_ShouldReturnValidationProblems_WhenRequestIsNotValid()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var createCarRequest = new CreateCar.Request("");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
|
||||||
|
var validationProblemDetails = await response.Content.ReadFromJsonAsync<ValidationProblemDetails>();
|
||||||
|
validationProblemDetails!.Errors.Keys.Should().Contain(x =>
|
||||||
|
x.Equals(nameof(CreateCar.Request.Name), StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
_dbContext.Cars.Should().NotContainEquivalentOf(createCarRequest, o => o.ExcludingMissingMembers());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InitializeAsync() => Task.CompletedTask;
|
||||||
|
|
||||||
|
public async Task DisposeAsync()
|
||||||
|
{
|
||||||
|
_scope.Dispose();
|
||||||
|
await _dbContext.DisposeAsync();
|
||||||
|
await _factory.ResetDatabaseAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
64
tests/WebApi.Tests.Integration/Cars/DeleteCarTests.cs
Normal file
64
tests/WebApi.Tests.Integration/Cars/DeleteCarTests.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using Vegasco.WebApi.Cars;
|
||||||
|
using Vegasco.WebApi.Persistence;
|
||||||
|
|
||||||
|
namespace WebApi.Tests.Integration.Cars;
|
||||||
|
|
||||||
|
[Collection(SharedTestCollection.Name)]
|
||||||
|
public class DeleteCarTests : IAsyncLifetime
|
||||||
|
{
|
||||||
|
private readonly WebAppFactory _factory;
|
||||||
|
private readonly IServiceScope _scope;
|
||||||
|
private readonly ApplicationDbContext _dbContext;
|
||||||
|
|
||||||
|
private readonly CarFaker _carFaker = new();
|
||||||
|
|
||||||
|
public DeleteCarTests(WebAppFactory factory)
|
||||||
|
{
|
||||||
|
_factory = factory;
|
||||||
|
_scope = _factory.Services.CreateScope();
|
||||||
|
_dbContext = _scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteCar_ShouldReturnNotFound_WhenCarDoesNotExist()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var randomCarId = Guid.NewGuid();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _factory.HttpClient.DeleteAsync($"v1/cars/{randomCarId}");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteCar_ShouldDeleteCar_WhenCarExists()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var createCarRequest = _carFaker.CreateCarRequest();
|
||||||
|
var createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest);
|
||||||
|
createCarResponse.EnsureSuccessStatusCode();
|
||||||
|
var createdCar = await createCarResponse.Content.ReadFromJsonAsync<CreateCar.Response>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _factory.HttpClient.DeleteAsync($"v1/cars/{createdCar!.Id}");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.NoContent);
|
||||||
|
_dbContext.Cars.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InitializeAsync() => Task.CompletedTask;
|
||||||
|
|
||||||
|
public async Task DisposeAsync()
|
||||||
|
{
|
||||||
|
_scope.Dispose();
|
||||||
|
await _dbContext.DisposeAsync();
|
||||||
|
await _factory.ResetDatabaseAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
57
tests/WebApi.Tests.Integration/Cars/GetCarTests.cs
Normal file
57
tests/WebApi.Tests.Integration/Cars/GetCarTests.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using Vegasco.WebApi.Cars;
|
||||||
|
|
||||||
|
namespace WebApi.Tests.Integration.Cars;
|
||||||
|
|
||||||
|
[Collection(SharedTestCollection.Name)]
|
||||||
|
public class GetCarTests : IAsyncLifetime
|
||||||
|
{
|
||||||
|
private readonly WebAppFactory _factory;
|
||||||
|
|
||||||
|
private readonly CarFaker _carFaker = new();
|
||||||
|
|
||||||
|
public GetCarTests(WebAppFactory factory)
|
||||||
|
{
|
||||||
|
_factory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetCar_ShouldReturnNotFound_WhenCarDoesNotExist()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var randomCarId = Guid.NewGuid();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _factory.HttpClient.GetAsync($"v1/cars/{randomCarId}");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetCar_ShouldReturnCar_WhenCarExists()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var createCarRequest = _carFaker.CreateCarRequest();
|
||||||
|
var createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest);
|
||||||
|
createCarResponse.EnsureSuccessStatusCode();
|
||||||
|
var createdCar = await createCarResponse.Content.ReadFromJsonAsync<CreateCar.Response>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _factory.HttpClient.GetAsync($"v1/cars/{createdCar!.Id}");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||||
|
var car = await response.Content.ReadFromJsonAsync<GetCar.Response>();
|
||||||
|
car.Should().BeEquivalentTo(createdCar);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InitializeAsync() => Task.CompletedTask;
|
||||||
|
|
||||||
|
public async Task DisposeAsync()
|
||||||
|
{
|
||||||
|
await _factory.ResetDatabaseAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
66
tests/WebApi.Tests.Integration/Cars/GetCarsTests.cs
Normal file
66
tests/WebApi.Tests.Integration/Cars/GetCarsTests.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using Vegasco.WebApi.Cars;
|
||||||
|
|
||||||
|
namespace WebApi.Tests.Integration.Cars;
|
||||||
|
|
||||||
|
[Collection(SharedTestCollection.Name)]
|
||||||
|
public class GetCarsTests : IAsyncLifetime
|
||||||
|
{
|
||||||
|
private readonly WebAppFactory _factory;
|
||||||
|
|
||||||
|
private readonly CarFaker _carFaker = new();
|
||||||
|
|
||||||
|
public GetCarsTests(WebAppFactory factory)
|
||||||
|
{
|
||||||
|
_factory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetCars_ShouldReturnEmptyList_WhenNoEntriesExist()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _factory.HttpClient.GetAsync("v1/cars");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||||
|
var cars = await response.Content.ReadFromJsonAsync<IEnumerable<GetCars.Response>>();
|
||||||
|
cars.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetCars_ShouldReturnEntries_WhenEntriesExist()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
List<CreateCar.Response> createdCars = [];
|
||||||
|
|
||||||
|
const int numberOfCars = 5;
|
||||||
|
for (var i = 0; i < numberOfCars; i++)
|
||||||
|
{
|
||||||
|
var createCarRequest = _carFaker.CreateCarRequest();
|
||||||
|
var createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest);
|
||||||
|
createCarResponse.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var createdCar = await createCarResponse.Content.ReadFromJsonAsync<CreateCar.Response>();
|
||||||
|
createdCars.Add(createdCar!);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _factory.HttpClient.GetAsync("v1/cars");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||||
|
var cars = await response.Content.ReadFromJsonAsync<IEnumerable<GetCars.Response>>();
|
||||||
|
cars.Should().BeEquivalentTo(createdCars);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InitializeAsync() => Task.CompletedTask;
|
||||||
|
|
||||||
|
public async Task DisposeAsync()
|
||||||
|
{
|
||||||
|
await _factory.ResetDatabaseAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
99
tests/WebApi.Tests.Integration/Cars/UpdateCarTests.cs
Normal file
99
tests/WebApi.Tests.Integration/Cars/UpdateCarTests.cs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using Vegasco.WebApi.Cars;
|
||||||
|
using Vegasco.WebApi.Persistence;
|
||||||
|
|
||||||
|
namespace WebApi.Tests.Integration.Cars;
|
||||||
|
|
||||||
|
[Collection(SharedTestCollection.Name)]
|
||||||
|
public class UpdateCarTests : IAsyncLifetime
|
||||||
|
{
|
||||||
|
private readonly WebAppFactory _factory;
|
||||||
|
private readonly IServiceScope _scope;
|
||||||
|
private readonly ApplicationDbContext _dbContext;
|
||||||
|
|
||||||
|
private readonly CarFaker _carFaker = new();
|
||||||
|
|
||||||
|
public UpdateCarTests(WebAppFactory factory)
|
||||||
|
{
|
||||||
|
_factory = factory;
|
||||||
|
_scope = _factory.Services.CreateScope();
|
||||||
|
_dbContext = _scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateCar_ShouldUpdateCar_WhenCarExistsAndRequestIsValid()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var createCarRequest = _carFaker.CreateCarRequest();
|
||||||
|
var createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest);
|
||||||
|
createCarResponse.EnsureSuccessStatusCode();
|
||||||
|
var createdCar = await createCarResponse.Content.ReadFromJsonAsync<CreateCar.Response>();
|
||||||
|
|
||||||
|
var updateCarRequest = _carFaker.UpdateCarRequest();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _factory.HttpClient.PutAsJsonAsync($"v1/cars/{createdCar!.Id}", updateCarRequest);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||||
|
var updatedCar = await response.Content.ReadFromJsonAsync<CreateCar.Response>();
|
||||||
|
updatedCar!.Id.Should().Be(createdCar.Id);
|
||||||
|
updatedCar.Should().BeEquivalentTo(updateCarRequest, o => o.ExcludingMissingMembers());
|
||||||
|
|
||||||
|
_dbContext.Cars.Should().ContainEquivalentOf(updatedCar, o => o.ExcludingMissingMembers());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateCar_ShouldReturnValidationProblems_WhenRequestIsNotValid()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var createCarRequest = _carFaker.CreateCarRequest();
|
||||||
|
var createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest);
|
||||||
|
createCarResponse.EnsureSuccessStatusCode();
|
||||||
|
var createdCar = await createCarResponse.Content.ReadFromJsonAsync<CreateCar.Response>();
|
||||||
|
|
||||||
|
var updateCarRequest = new UpdateCar.Request("");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _factory.HttpClient.PutAsJsonAsync($"v1/cars/{createdCar!.Id}", updateCarRequest);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
|
||||||
|
var validationProblemDetails = await response.Content.ReadFromJsonAsync<ValidationProblemDetails>();
|
||||||
|
validationProblemDetails!.Errors.Keys.Should().Contain(x =>
|
||||||
|
x.Equals(nameof(CreateCar.Request.Name), StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
_dbContext.Cars.Should().ContainSingle(x => x.Id == createdCar.Id)
|
||||||
|
.Which
|
||||||
|
.Should().NotBeEquivalentTo(updateCarRequest, o => o.ExcludingMissingMembers());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateCar_ShouldReturnNotFound_WhenNoCarWithIdExists()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var updateCarRequest = _carFaker.UpdateCarRequest();
|
||||||
|
var randomCarId = Guid.NewGuid();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _factory.HttpClient.PutAsJsonAsync($"v1/cars/{randomCarId}", updateCarRequest);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
|
||||||
|
|
||||||
|
_dbContext.Cars.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InitializeAsync() => Task.CompletedTask;
|
||||||
|
|
||||||
|
public async Task DisposeAsync()
|
||||||
|
{
|
||||||
|
_scope.Dispose();
|
||||||
|
await _dbContext.DisposeAsync();
|
||||||
|
await _factory.ResetDatabaseAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
40
tests/WebApi.Tests.Integration/PostgresRespawner.cs
Normal file
40
tests/WebApi.Tests.Integration/PostgresRespawner.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using Npgsql;
|
||||||
|
using Respawn;
|
||||||
|
using System.Data.Common;
|
||||||
|
|
||||||
|
namespace WebApi.Tests.Integration;
|
||||||
|
internal sealed class PostgresRespawner : IDisposable
|
||||||
|
{
|
||||||
|
private readonly DbConnection _connection;
|
||||||
|
private readonly Respawner _respawner;
|
||||||
|
|
||||||
|
private PostgresRespawner(Respawner respawner, DbConnection connection)
|
||||||
|
{
|
||||||
|
_respawner = respawner;
|
||||||
|
_connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<PostgresRespawner> CreateAsync(string connectionString)
|
||||||
|
{
|
||||||
|
DbConnection connection = new NpgsqlConnection(connectionString);
|
||||||
|
await connection.OpenAsync();
|
||||||
|
|
||||||
|
var respawner = await Respawner.CreateAsync(connection,
|
||||||
|
new RespawnerOptions
|
||||||
|
{
|
||||||
|
SchemasToInclude = ["public"],
|
||||||
|
DbAdapter = DbAdapter.Postgres
|
||||||
|
});
|
||||||
|
return new PostgresRespawner(respawner, connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ResetDatabaseAsync()
|
||||||
|
{
|
||||||
|
await _respawner.ResetAsync(_connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_connection.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
7
tests/WebApi.Tests.Integration/SharedTestCollection.cs
Normal file
7
tests/WebApi.Tests.Integration/SharedTestCollection.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace WebApi.Tests.Integration;
|
||||||
|
|
||||||
|
[CollectionDefinition(Name)]
|
||||||
|
public class SharedTestCollection : ICollectionFixture<WebAppFactory>
|
||||||
|
{
|
||||||
|
public const string Name = nameof(SharedTestCollection);
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Authorization.Policy;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using System.Security.Claims;
|
||||||
|
|
||||||
|
namespace WebApi.Tests.Integration;
|
||||||
|
|
||||||
|
public sealed class TestUserAlwaysAuthorizedPolicyEvaluator : IPolicyEvaluator
|
||||||
|
{
|
||||||
|
public const string Username = "Test user";
|
||||||
|
public static readonly string UserId = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
public Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
|
||||||
|
{
|
||||||
|
Claim[] claims =
|
||||||
|
[
|
||||||
|
|
||||||
|
new Claim(ClaimTypes.Name, Username),
|
||||||
|
new Claim("name", Username),
|
||||||
|
new Claim(ClaimTypes.NameIdentifier, UserId),
|
||||||
|
new Claim("aud", "https://localhost")
|
||||||
|
];
|
||||||
|
|
||||||
|
ClaimsIdentity identity = new(claims, JwtBearerDefaults.AuthenticationScheme);
|
||||||
|
ClaimsPrincipal principal = new(identity);
|
||||||
|
AuthenticationTicket ticket = new(principal, JwtBearerDefaults.AuthenticationScheme);
|
||||||
|
var result = AuthenticateResult.Success(ticket);
|
||||||
|
return Task.FromResult(result); ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context,
|
||||||
|
object? resource)
|
||||||
|
{
|
||||||
|
return Task.FromResult(PolicyAuthorizationResult.Success());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Bogus" Version="35.6.0" />
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||||
|
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.7" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||||
|
<PackageReference Include="Respawn" Version="6.2.1" />
|
||||||
|
<PackageReference Include="Testcontainers.PostgreSql" Version="3.9.0" />
|
||||||
|
<PackageReference Include="xunit" Version="2.5.3" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\WebApi\WebApi.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Xunit" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
79
tests/WebApi.Tests.Integration/WebAppFactory.cs
Normal file
79
tests/WebApi.Tests.Integration/WebAppFactory.cs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
using DotNet.Testcontainers.Images;
|
||||||
|
using Microsoft.AspNetCore.Authorization.Policy;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Testing;
|
||||||
|
using Microsoft.AspNetCore.TestHost;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
using Testcontainers.PostgreSql;
|
||||||
|
using Vegasco.WebApi.Common;
|
||||||
|
using Vegasco.WebApi.Persistence;
|
||||||
|
|
||||||
|
namespace WebApi.Tests.Integration;
|
||||||
|
|
||||||
|
public sealed class WebAppFactory : WebApplicationFactory<IWebApiMarker>, IAsyncLifetime
|
||||||
|
{
|
||||||
|
private readonly PostgreSqlContainer _database = new PostgreSqlBuilder()
|
||||||
|
.WithImage(DockerImage)
|
||||||
|
.WithImagePullPolicy(PullPolicy.Always)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
private const string DockerImage = "postgres:16.3-alpine";
|
||||||
|
|
||||||
|
public HttpClient HttpClient => CreateClient();
|
||||||
|
|
||||||
|
private PostgresRespawner? _postgresRespawner;
|
||||||
|
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
await _database.StartAsync();
|
||||||
|
|
||||||
|
// Force application startup (i.e. initialization and validation)
|
||||||
|
_ = CreateClient();
|
||||||
|
|
||||||
|
using var scope = Services.CreateScope();
|
||||||
|
await using var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
await dbContext.Database.MigrateAsync();
|
||||||
|
|
||||||
|
_postgresRespawner = await PostgresRespawner.CreateAsync(_database.GetConnectionString());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||||
|
{
|
||||||
|
IEnumerable<KeyValuePair<string, string?>> customConfig =
|
||||||
|
[
|
||||||
|
new KeyValuePair<string, string?>("ConnectionStrings:Database", _database.GetConnectionString()),
|
||||||
|
new KeyValuePair<string, string?>("JWT:Authority", "https://localhost"),
|
||||||
|
new KeyValuePair<string, string?>("JWT:Audience", "https://localhost"),
|
||||||
|
new KeyValuePair<string, string?>("JWT:Issuer", "https://localhost"),
|
||||||
|
new KeyValuePair<string, string?>("JWT:NameClaimType", null),
|
||||||
|
];
|
||||||
|
|
||||||
|
builder.UseConfiguration(new ConfigurationBuilder()
|
||||||
|
.AddInMemoryCollection(customConfig)
|
||||||
|
.Build());
|
||||||
|
|
||||||
|
builder.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.ConfigureTestServices(services =>
|
||||||
|
{
|
||||||
|
services.RemoveAll<IPolicyEvaluator>();
|
||||||
|
services.AddSingleton<IPolicyEvaluator, TestUserAlwaysAuthorizedPolicyEvaluator>();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ResetDatabaseAsync()
|
||||||
|
{
|
||||||
|
await _postgresRespawner!.ResetDatabaseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task IAsyncLifetime.DisposeAsync()
|
||||||
|
{
|
||||||
|
_postgresRespawner!.Dispose();
|
||||||
|
await _database.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{437DE053-1DAB-4EEF-BEA6-E3B5179692F8}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{437DE053-1DAB-4EEF-BEA6-E3B5179692F8}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi.Tests.Unit", "tests\WebApi.Tests.Unit\WebApi.Tests.Unit.csproj", "{5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi.Tests.Unit", "tests\WebApi.Tests.Unit\WebApi.Tests.Unit.csproj", "{5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi.Tests.Integration", "tests\WebApi.Tests.Integration\WebApi.Tests.Integration.csproj", "{0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
@@ -30,6 +32,10 @@ Global
|
|||||||
{5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Release|Any CPU.Build.0 = Release|Any CPU
|
{5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -37,6 +43,7 @@ Global
|
|||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{9FF3C98A-5085-4EBE-A980-DB2148B0C00A} = {C051A684-BD6A-43F2-B0CC-F3C2315D99E3}
|
{9FF3C98A-5085-4EBE-A980-DB2148B0C00A} = {C051A684-BD6A-43F2-B0CC-F3C2315D99E3}
|
||||||
{5BA94D65-1D04-49EA-B7CC-F3719DE2D97E} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8}
|
{5BA94D65-1D04-49EA-B7CC-F3719DE2D97E} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8}
|
||||||
|
{0B1F3D81-95E8-4CFC-8A90-8A3CB2549326} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {7813E32D-AE19-479C-853B-063882D2D05A}
|
SolutionGuid = {7813E32D-AE19-479C-853B-063882D2D05A}
|
||||||
|
|||||||
3
vegasco-server.sln.DotSettings
Normal file
3
vegasco-server.sln.DotSettings
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=respawner/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Vegasco/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
Reference in New Issue
Block a user