Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Net;
using System.Net.Http.Headers;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using MinApiTests.IntegrationTests.Helpers;

namespace MinApiTests.IntegrationTests;

public class AuthorizedEndpointsIntegrationTests : IClassFixture<TestWebApplicationFactory<Program>>
{
private readonly TestWebApplicationFactory<Program> _factory;

public AuthorizedEndpointsIntegrationTests(TestWebApplicationFactory<Program> factory)
{
_factory = factory;
}

public static IEnumerable<object[]> AdminFlags => new List<object[]>
{
new object[] { "false", HttpStatusCode.Forbidden },
new object[] { "true", HttpStatusCode.OK }
};

[Theory]
[MemberData(nameof(AdminFlags))]
public async Task GetAdminEndpointIsReturnedForAnAuthorizedRequest(string isAdmin, HttpStatusCode code)
{
// Arrange
var client = _factory.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddAuthentication("Test")
.AddScheme<TestAuthenticationSchemeOptions, TestAuthHandler>("Test",
options => options.IsAdmin = isAdmin);
});
}).CreateClient();

client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Test");

//Act
var response = await client.GetAsync("/admin");

// Assert
Assert.Equal(code, response.StatusCode);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Security.Claims;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace MinApiTests.IntegrationTests.Helpers;

public class TestAuthenticationSchemeOptions : AuthenticationSchemeOptions
{
public string IsAdmin { get; set; } = "false";
}

public class TestAuthHandler : AuthenticationHandler<TestAuthenticationSchemeOptions>
{
public TestAuthHandler(IOptionsMonitor<TestAuthenticationSchemeOptions> options,
ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
}

protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var claims = new[]
{
new Claim("admin", Options.IsAdmin)
};

var identity = new ClaimsIdentity(claims, "Test");
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, "Test");

var result = AuthenticateResult.Success(ticket);

return Task.FromResult(result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using WebMinRouteGroup.Services;

namespace MinApiTests.IntegrationTests.Helpers;

public class TestEmailService : IEmailService
{
public Task Send(string emailAddress, string body)
{
// You don't want to send real email when running integration tests
return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using WebMinRouteGroup.Data;

namespace MinApiTests.IntegrationTests.Helpers;

public class TestWebApplicationFactory<TProgram>
: WebApplicationFactory<TProgram> where TProgram : class
{
protected override IHost CreateHost(IHostBuilder builder)
{
builder.ConfigureServices(services =>
{
var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<TodoGroupDbContext>));

if (descriptor != null)
{
services.Remove(descriptor);
}

services.AddDbContext<TodoGroupDbContext>(options =>
{
var path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
options.UseSqlite($"Data Source={Path.Join(path, "WebMinRouteGroup_tests.db")}");
});
});

return base.CreateHost(builder);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System.Net;
using System.Net.Http.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using MinApiTests.IntegrationTests.Helpers;
using WebMinRouteGroup.Data;

namespace MinApiTests.IntegrationTests;

[Collection("Sequential")]
public class TodoIntegrationTestsV1 : IClassFixture<TestWebApplicationFactory<Program>>
{
private readonly TestWebApplicationFactory<Program> _factory;
private readonly HttpClient _httpClient;

public TodoIntegrationTestsV1(TestWebApplicationFactory<Program> factory)
{
_factory = factory;
_httpClient = factory.CreateClient();
}

public static IEnumerable<object[]> InvalidTodos => new List<object[]>
{
new object[] { new TodoDto { Title = "", Description = "Test description", IsDone = false }, "Name is empty" },
new object[] { new TodoDto { Title = "no", Description = "Test description", IsDone = false }, "Name length < 3" }
};

[Theory]
[MemberData(nameof(InvalidTodos))]
public async Task PostTodoWithValidationProblems(TodoDto todo, string errorMessage)
{
var response = await _httpClient.PostAsJsonAsync("/todos/v1", todo);

Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);

var problemResult = await response.Content.ReadFromJsonAsync<HttpValidationProblemDetails>();

Assert.NotNull(problemResult?.Errors);
Assert.Collection(problemResult.Errors, (error) => Assert.Equal(errorMessage, error.Value.First()));
}

[Fact]
public async Task PostTodoWithValidParameters()
{
using (var scope = _factory.Services.CreateScope())
{
var db = scope.ServiceProvider.GetService<TodoGroupDbContext>();
if (db != null && db.Todos.Any())
{
db.Todos.RemoveRange(db.Todos);
await db.SaveChangesAsync();
}
}

var response = await _httpClient.PostAsJsonAsync("/todos/v1", new TodoDto
{
Title = "Test title",
Description = "Test description",
IsDone = false
});

Assert.Equal(HttpStatusCode.Created, response.StatusCode);

var todos = await _httpClient.GetFromJsonAsync<List<Todo>>("/todos/v1");

Assert.NotNull(todos);
Assert.Single(todos);

Assert.Collection(todos, (todo) =>
{
Assert.Equal("Test title", todo.Title);
Assert.Equal("Test description", todo.Description);
Assert.False(todo.IsDone);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System.Net;
using System.Net.Http.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using MinApiTests.IntegrationTests.Helpers;
using WebMinRouteGroup.Data;
using WebMinRouteGroup.Services;

namespace MinApiTests.IntegrationTests;

[Collection("Sequential")]
public class TodoIntegrationTestsV2 : IClassFixture<TestWebApplicationFactory<Program>>
{
private readonly TestWebApplicationFactory<Program> _factory;
private readonly HttpClient _httpClient;

public TodoIntegrationTestsV2(TestWebApplicationFactory<Program> factory)
{
_factory = factory;
_httpClient = factory.CreateClient();
}

public static IEnumerable<object[]> InvalidTodos => new List<object[]>
{
new object[] { new TodoDto { Title = "", Description = "Test description", IsDone = false }, "Name is empty" },
new object[] { new TodoDto { Title = "no", Description = "Test description", IsDone = false }, "Name length < 3" }
};

[Theory]
[MemberData(nameof(InvalidTodos))]
public async Task PostTodoWithValidationProblems(TodoDto todo, string errorMessage)
{
var response = await _httpClient.PostAsJsonAsync("/todos/v2", todo);

Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);

var problemResult = await response.Content.ReadFromJsonAsync<HttpValidationProblemDetails>();

Assert.NotNull(problemResult?.Errors);
Assert.Collection(problemResult.Errors, (error) => Assert.Equal(errorMessage, error.Value.First()));
}

[Fact]
public async Task PostTodoWithValidParameters()
{
using (var scope = _factory.Services.CreateScope())
{
var db = scope.ServiceProvider.GetService<TodoGroupDbContext>();
if (db != null && db.Todos.Any())
{
db.Todos.RemoveRange(db.Todos);
await db.SaveChangesAsync();
}
}

var client = _factory.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddSingleton<IEmailService, TestEmailService>();
});
}).CreateClient();

var response = await client.PostAsJsonAsync("/todos/v2", new TodoDto
{
Title = "Test title",
Description = "Test description",
IsDone = false
});

Assert.Equal(HttpStatusCode.Created, response.StatusCode);

var todos = await client.GetFromJsonAsync<List<Todo>>("/todos/v2");

Assert.NotNull(todos);
Assert.Single(todos);

Assert.Collection(todos, (todo) =>
{
Assert.Equal("Test title", todo.Title);
Assert.Equal("Test description", todo.Description);
Assert.False(todo.IsDone);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.0-rc.1.22427.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.0-rc.1.22426.7" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0-preview-20220726-02" />
<PackageReference Include="Moq" Version="4.18.2" />
<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>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\WebMinRouteGroup\WebMinRouteGroup.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.EntityFrameworkCore;
using WebMinRouteGroup.Data;

namespace MinApiTests.UnitTests.Helpers;

public class MockDb : IDbContextFactory<TodoGroupDbContext>
{
public TodoGroupDbContext CreateDbContext()
{
var options = new DbContextOptionsBuilder<TodoGroupDbContext>()
.UseInMemoryDatabase($"InMemoryTestDb-{DateTime.Now.ToFileTimeUtc()}")
.Options;

return new TodoGroupDbContext(options);
}
}
Loading