First commit. Include junk from template to remove
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<RootNamespace>Hutopy.Application.FunctionalTests</RootNamespace>
|
||||
<AssemblyName>Hutopy.Application.FunctionalTests</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="appsettings.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="appsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="nunit" />
|
||||
<PackageReference Include="NUnit.Analyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NUnit3TestAdapter" />
|
||||
<PackageReference Include="coverlet.collector" />
|
||||
<PackageReference Include="FluentAssertions" />
|
||||
<PackageReference Include="Moq" />
|
||||
<PackageReference Include="Respawn" />
|
||||
<PackageReference Include="Testcontainers.MsSql" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Web\Web.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
13
tests/Application.FunctionalTests/BaseTestFixture.cs
Normal file
13
tests/Application.FunctionalTests/BaseTestFixture.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Hutopy.Application.FunctionalTests;
|
||||
|
||||
using static Testing;
|
||||
|
||||
[TestFixture]
|
||||
public abstract class BaseTestFixture
|
||||
{
|
||||
[SetUp]
|
||||
public async Task TestSetUp()
|
||||
{
|
||||
await ResetState();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Data.Common;
|
||||
using Hutopy.Application.Common.Interfaces;
|
||||
using Hutopy.Infrastructure.Data;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace Hutopy.Application.FunctionalTests;
|
||||
|
||||
using static Testing;
|
||||
|
||||
public class CustomWebApplicationFactory : WebApplicationFactory<Program>
|
||||
{
|
||||
private readonly DbConnection _connection;
|
||||
|
||||
public CustomWebApplicationFactory(DbConnection connection)
|
||||
{
|
||||
_connection = connection;
|
||||
}
|
||||
|
||||
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||
{
|
||||
builder.ConfigureTestServices(services =>
|
||||
{
|
||||
services
|
||||
.RemoveAll<IUser>()
|
||||
.AddTransient(provider => Mock.Of<IUser>(s => s.Id == GetUserId()));
|
||||
|
||||
services
|
||||
.RemoveAll<DbContextOptions<ApplicationDbContext>>()
|
||||
.AddDbContext<ApplicationDbContext>((sp, options) =>
|
||||
{
|
||||
options.AddInterceptors(sp.GetServices<ISaveChangesInterceptor>());
|
||||
options.UseSqlServer(_connection);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
4
tests/Application.FunctionalTests/GlobalUsings.cs
Normal file
4
tests/Application.FunctionalTests/GlobalUsings.cs
Normal file
@@ -0,0 +1,4 @@
|
||||
global using Ardalis.GuardClauses;
|
||||
global using FluentAssertions;
|
||||
global using Moq;
|
||||
global using NUnit.Framework;
|
||||
14
tests/Application.FunctionalTests/ITestDatabase.cs
Normal file
14
tests/Application.FunctionalTests/ITestDatabase.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Data.Common;
|
||||
|
||||
namespace Hutopy.Application.FunctionalTests;
|
||||
|
||||
public interface ITestDatabase
|
||||
{
|
||||
Task InitialiseAsync();
|
||||
|
||||
DbConnection GetConnection();
|
||||
|
||||
Task ResetAsync();
|
||||
|
||||
Task DisposeAsync();
|
||||
}
|
||||
62
tests/Application.FunctionalTests/SqlServerTestDatabase.cs
Normal file
62
tests/Application.FunctionalTests/SqlServerTestDatabase.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System.Data.Common;
|
||||
using Hutopy.Infrastructure.Data;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Respawn;
|
||||
|
||||
namespace Hutopy.Application.FunctionalTests;
|
||||
|
||||
public class SqlServerTestDatabase : ITestDatabase
|
||||
{
|
||||
private readonly string _connectionString = null!;
|
||||
private SqlConnection _connection = null!;
|
||||
private Respawner _respawner = null!;
|
||||
|
||||
public SqlServerTestDatabase()
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddJsonFile("appsettings.json")
|
||||
.AddEnvironmentVariables()
|
||||
.Build();
|
||||
|
||||
var connectionString = configuration.GetConnectionString("DefaultConnection");
|
||||
|
||||
Guard.Against.Null(connectionString);
|
||||
|
||||
_connectionString = connectionString;
|
||||
}
|
||||
|
||||
public async Task InitialiseAsync()
|
||||
{
|
||||
_connection = new SqlConnection(_connectionString);
|
||||
|
||||
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||
.UseSqlServer(_connectionString)
|
||||
.Options;
|
||||
|
||||
var context = new ApplicationDbContext(options);
|
||||
|
||||
context.Database.Migrate();
|
||||
|
||||
_respawner = await Respawner.CreateAsync(_connectionString, new RespawnerOptions
|
||||
{
|
||||
TablesToIgnore = new Respawn.Graph.Table[] { "__EFMigrationsHistory" }
|
||||
});
|
||||
}
|
||||
|
||||
public DbConnection GetConnection()
|
||||
{
|
||||
return _connection;
|
||||
}
|
||||
|
||||
public async Task ResetAsync()
|
||||
{
|
||||
await _respawner.ResetAsync(_connectionString);
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
await _connection.DisposeAsync();
|
||||
}
|
||||
}
|
||||
13
tests/Application.FunctionalTests/TestDatabaseFactory.cs
Normal file
13
tests/Application.FunctionalTests/TestDatabaseFactory.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Hutopy.Application.FunctionalTests;
|
||||
|
||||
public static class TestDatabaseFactory
|
||||
{
|
||||
public static async Task<ITestDatabase> CreateAsync()
|
||||
{
|
||||
var database = new TestcontainersTestDatabase();
|
||||
|
||||
await database.InitialiseAsync();
|
||||
|
||||
return database;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using System.Data.Common;
|
||||
using Hutopy.Infrastructure.Data;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Respawn;
|
||||
using Testcontainers.MsSql;
|
||||
|
||||
namespace Hutopy.Application.FunctionalTests;
|
||||
|
||||
public class TestcontainersTestDatabase : ITestDatabase
|
||||
{
|
||||
private readonly MsSqlContainer _container;
|
||||
private DbConnection _connection = null!;
|
||||
private string _connectionString = null!;
|
||||
private Respawner _respawner = null!;
|
||||
|
||||
public TestcontainersTestDatabase()
|
||||
{
|
||||
_container = new MsSqlBuilder()
|
||||
.WithAutoRemove(true)
|
||||
.Build();
|
||||
}
|
||||
|
||||
public async Task InitialiseAsync()
|
||||
{
|
||||
await _container.StartAsync();
|
||||
|
||||
_connectionString = _container.GetConnectionString();
|
||||
|
||||
_connection = new SqlConnection(_connectionString);
|
||||
|
||||
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||
.UseSqlServer(_connectionString)
|
||||
.Options;
|
||||
|
||||
var context = new ApplicationDbContext(options);
|
||||
|
||||
context.Database.Migrate();
|
||||
|
||||
_respawner = await Respawner.CreateAsync(_connectionString, new RespawnerOptions
|
||||
{
|
||||
TablesToIgnore = new Respawn.Graph.Table[] { "__EFMigrationsHistory" }
|
||||
});
|
||||
}
|
||||
|
||||
public DbConnection GetConnection()
|
||||
{
|
||||
return _connection;
|
||||
}
|
||||
|
||||
public async Task ResetAsync()
|
||||
{
|
||||
await _respawner.ResetAsync(_connectionString);
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
await _connection.DisposeAsync();
|
||||
await _container.DisposeAsync();
|
||||
}
|
||||
}
|
||||
146
tests/Application.FunctionalTests/Testing.cs
Normal file
146
tests/Application.FunctionalTests/Testing.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
using Hutopy.Domain.Constants;
|
||||
using Hutopy.Infrastructure.Data;
|
||||
using Hutopy.Infrastructure.Identity;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Hutopy.Application.FunctionalTests;
|
||||
|
||||
[SetUpFixture]
|
||||
public partial class Testing
|
||||
{
|
||||
private static ITestDatabase _database;
|
||||
private static CustomWebApplicationFactory _factory = null!;
|
||||
private static IServiceScopeFactory _scopeFactory = null!;
|
||||
private static string? _userId;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public async Task RunBeforeAnyTests()
|
||||
{
|
||||
_database = await TestDatabaseFactory.CreateAsync();
|
||||
|
||||
_factory = new CustomWebApplicationFactory(_database.GetConnection());
|
||||
|
||||
_scopeFactory = _factory.Services.GetRequiredService<IServiceScopeFactory>();
|
||||
}
|
||||
|
||||
public static async Task<TResponse> SendAsync<TResponse>(IRequest<TResponse> request)
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
|
||||
var mediator = scope.ServiceProvider.GetRequiredService<ISender>();
|
||||
|
||||
return await mediator.Send(request);
|
||||
}
|
||||
|
||||
public static async Task SendAsync(IBaseRequest request)
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
|
||||
var mediator = scope.ServiceProvider.GetRequiredService<ISender>();
|
||||
|
||||
await mediator.Send(request);
|
||||
}
|
||||
|
||||
public static string? GetUserId()
|
||||
{
|
||||
return _userId;
|
||||
}
|
||||
|
||||
public static async Task<string> RunAsDefaultUserAsync()
|
||||
{
|
||||
return await RunAsUserAsync("test@local", "Testing1234!", Array.Empty<string>());
|
||||
}
|
||||
|
||||
public static async Task<string> RunAsAdministratorAsync()
|
||||
{
|
||||
return await RunAsUserAsync("administrator@local", "Administrator1234!", new[] { Roles.Administrator });
|
||||
}
|
||||
|
||||
public static async Task<string> RunAsUserAsync(string userName, string password, string[] roles)
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
|
||||
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
|
||||
|
||||
var user = new ApplicationUser { UserName = userName, Email = userName };
|
||||
|
||||
var result = await userManager.CreateAsync(user, password);
|
||||
|
||||
if (roles.Any())
|
||||
{
|
||||
var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
|
||||
|
||||
foreach (var role in roles)
|
||||
{
|
||||
await roleManager.CreateAsync(new IdentityRole(role));
|
||||
}
|
||||
|
||||
await userManager.AddToRolesAsync(user, roles);
|
||||
}
|
||||
|
||||
if (result.Succeeded)
|
||||
{
|
||||
_userId = user.Id;
|
||||
|
||||
return _userId;
|
||||
}
|
||||
|
||||
var errors = string.Join(Environment.NewLine, result.ToApplicationResult().Errors);
|
||||
|
||||
throw new Exception($"Unable to create {userName}.{Environment.NewLine}{errors}");
|
||||
}
|
||||
|
||||
public static async Task ResetState()
|
||||
{
|
||||
try
|
||||
{
|
||||
await _database.ResetAsync();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
_userId = null;
|
||||
}
|
||||
|
||||
public static async Task<TEntity?> FindAsync<TEntity>(params object[] keyValues)
|
||||
where TEntity : class
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
|
||||
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
return await context.FindAsync<TEntity>(keyValues);
|
||||
}
|
||||
|
||||
public static async Task AddAsync<TEntity>(TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
|
||||
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
context.Add(entity);
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public static async Task<int> CountAsync<TEntity>() where TEntity : class
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
|
||||
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
return await context.Set<TEntity>().CountAsync();
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public async Task RunAfterAnyTests()
|
||||
{
|
||||
await _database.DisposeAsync();
|
||||
await _factory.DisposeAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using Hutopy.Application.Common.Exceptions;
|
||||
using Hutopy.Application.TodoItems.Commands.CreateTodoItem;
|
||||
using Hutopy.Application.TodoLists.Commands.CreateTodoList;
|
||||
using Hutopy.Domain.Entities;
|
||||
|
||||
namespace Hutopy.Application.FunctionalTests.TodoItems.Commands;
|
||||
|
||||
using static Testing;
|
||||
|
||||
public class CreateTodoItemTests : BaseTestFixture
|
||||
{
|
||||
[Test]
|
||||
public async Task ShouldRequireMinimumFields()
|
||||
{
|
||||
var command = new CreateTodoItemCommand();
|
||||
|
||||
await FluentActions.Invoking(() =>
|
||||
SendAsync(command)).Should().ThrowAsync<ValidationException>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ShouldCreateTodoItem()
|
||||
{
|
||||
var userId = await RunAsDefaultUserAsync();
|
||||
|
||||
var listId = await SendAsync(new CreateTodoListCommand
|
||||
{
|
||||
Title = "New List"
|
||||
});
|
||||
|
||||
var command = new CreateTodoItemCommand
|
||||
{
|
||||
ListId = listId,
|
||||
Title = "Tasks"
|
||||
};
|
||||
|
||||
var itemId = await SendAsync(command);
|
||||
|
||||
var item = await FindAsync<TodoItem>(itemId);
|
||||
|
||||
item.Should().NotBeNull();
|
||||
item!.ListId.Should().Be(command.ListId);
|
||||
item.Title.Should().Be(command.Title);
|
||||
item.CreatedBy.Should().Be(userId);
|
||||
item.Created.Should().BeCloseTo(DateTime.Now, TimeSpan.FromMilliseconds(10000));
|
||||
item.LastModifiedBy.Should().Be(userId);
|
||||
item.LastModified.Should().BeCloseTo(DateTime.Now, TimeSpan.FromMilliseconds(10000));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Hutopy.Application.TodoItems.Commands.CreateTodoItem;
|
||||
using Hutopy.Application.TodoItems.Commands.DeleteTodoItem;
|
||||
using Hutopy.Application.TodoLists.Commands.CreateTodoList;
|
||||
using Hutopy.Domain.Entities;
|
||||
|
||||
namespace Hutopy.Application.FunctionalTests.TodoItems.Commands;
|
||||
|
||||
using static Testing;
|
||||
|
||||
public class DeleteTodoItemTests : BaseTestFixture
|
||||
{
|
||||
[Test]
|
||||
public async Task ShouldRequireValidTodoItemId()
|
||||
{
|
||||
var command = new DeleteTodoItemCommand(99);
|
||||
|
||||
await FluentActions.Invoking(() =>
|
||||
SendAsync(command)).Should().ThrowAsync<NotFoundException>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ShouldDeleteTodoItem()
|
||||
{
|
||||
var listId = await SendAsync(new CreateTodoListCommand
|
||||
{
|
||||
Title = "New List"
|
||||
});
|
||||
|
||||
var itemId = await SendAsync(new CreateTodoItemCommand
|
||||
{
|
||||
ListId = listId,
|
||||
Title = "New Item"
|
||||
});
|
||||
|
||||
await SendAsync(new DeleteTodoItemCommand(itemId));
|
||||
|
||||
var item = await FindAsync<TodoItem>(itemId);
|
||||
|
||||
item.Should().BeNull();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using Hutopy.Application.TodoItems.Commands.CreateTodoItem;
|
||||
using Hutopy.Application.TodoItems.Commands.UpdateTodoItem;
|
||||
using Hutopy.Application.TodoItems.Commands.UpdateTodoItemDetail;
|
||||
using Hutopy.Application.TodoLists.Commands.CreateTodoList;
|
||||
using Hutopy.Domain.Entities;
|
||||
using Hutopy.Domain.Enums;
|
||||
|
||||
namespace Hutopy.Application.FunctionalTests.TodoItems.Commands;
|
||||
|
||||
using static Testing;
|
||||
|
||||
public class UpdateTodoItemDetailTests : BaseTestFixture
|
||||
{
|
||||
[Test]
|
||||
public async Task ShouldRequireValidTodoItemId()
|
||||
{
|
||||
var command = new UpdateTodoItemCommand { Id = 99, Title = "New Title" };
|
||||
await FluentActions.Invoking(() => SendAsync(command)).Should().ThrowAsync<NotFoundException>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ShouldUpdateTodoItem()
|
||||
{
|
||||
var userId = await RunAsDefaultUserAsync();
|
||||
|
||||
var listId = await SendAsync(new CreateTodoListCommand
|
||||
{
|
||||
Title = "New List"
|
||||
});
|
||||
|
||||
var itemId = await SendAsync(new CreateTodoItemCommand
|
||||
{
|
||||
ListId = listId,
|
||||
Title = "New Item"
|
||||
});
|
||||
|
||||
var command = new UpdateTodoItemDetailCommand
|
||||
{
|
||||
Id = itemId,
|
||||
ListId = listId,
|
||||
Note = "This is the note.",
|
||||
Priority = PriorityLevel.High
|
||||
};
|
||||
|
||||
await SendAsync(command);
|
||||
|
||||
var item = await FindAsync<TodoItem>(itemId);
|
||||
|
||||
item.Should().NotBeNull();
|
||||
item!.ListId.Should().Be(command.ListId);
|
||||
item.Note.Should().Be(command.Note);
|
||||
item.Priority.Should().Be(command.Priority);
|
||||
item.LastModifiedBy.Should().NotBeNull();
|
||||
item.LastModifiedBy.Should().Be(userId);
|
||||
item.LastModified.Should().BeCloseTo(DateTime.Now, TimeSpan.FromMilliseconds(10000));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Hutopy.Application.TodoItems.Commands.CreateTodoItem;
|
||||
using Hutopy.Application.TodoItems.Commands.UpdateTodoItem;
|
||||
using Hutopy.Application.TodoLists.Commands.CreateTodoList;
|
||||
using Hutopy.Domain.Entities;
|
||||
|
||||
namespace Hutopy.Application.FunctionalTests.TodoItems.Commands;
|
||||
|
||||
using static Testing;
|
||||
|
||||
public class UpdateTodoItemTests : BaseTestFixture
|
||||
{
|
||||
[Test]
|
||||
public async Task ShouldRequireValidTodoItemId()
|
||||
{
|
||||
var command = new UpdateTodoItemCommand { Id = 99, Title = "New Title" };
|
||||
await FluentActions.Invoking(() => SendAsync(command)).Should().ThrowAsync<NotFoundException>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ShouldUpdateTodoItem()
|
||||
{
|
||||
var userId = await RunAsDefaultUserAsync();
|
||||
|
||||
var listId = await SendAsync(new CreateTodoListCommand
|
||||
{
|
||||
Title = "New List"
|
||||
});
|
||||
|
||||
var itemId = await SendAsync(new CreateTodoItemCommand
|
||||
{
|
||||
ListId = listId,
|
||||
Title = "New Item"
|
||||
});
|
||||
|
||||
var command = new UpdateTodoItemCommand
|
||||
{
|
||||
Id = itemId,
|
||||
Title = "Updated Item Title"
|
||||
};
|
||||
|
||||
await SendAsync(command);
|
||||
|
||||
var item = await FindAsync<TodoItem>(itemId);
|
||||
|
||||
item.Should().NotBeNull();
|
||||
item!.Title.Should().Be(command.Title);
|
||||
item.LastModifiedBy.Should().NotBeNull();
|
||||
item.LastModifiedBy.Should().Be(userId);
|
||||
item.LastModified.Should().BeCloseTo(DateTime.Now, TimeSpan.FromMilliseconds(10000));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using Hutopy.Application.Common.Exceptions;
|
||||
using Hutopy.Application.TodoLists.Commands.CreateTodoList;
|
||||
using Hutopy.Domain.Entities;
|
||||
|
||||
namespace Hutopy.Application.FunctionalTests.TodoLists.Commands;
|
||||
|
||||
using static Testing;
|
||||
|
||||
public class CreateTodoListTests : BaseTestFixture
|
||||
{
|
||||
[Test]
|
||||
public async Task ShouldRequireMinimumFields()
|
||||
{
|
||||
var command = new CreateTodoListCommand();
|
||||
await FluentActions.Invoking(() => SendAsync(command)).Should().ThrowAsync<ValidationException>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ShouldRequireUniqueTitle()
|
||||
{
|
||||
await SendAsync(new CreateTodoListCommand
|
||||
{
|
||||
Title = "Shopping"
|
||||
});
|
||||
|
||||
var command = new CreateTodoListCommand
|
||||
{
|
||||
Title = "Shopping"
|
||||
};
|
||||
|
||||
await FluentActions.Invoking(() =>
|
||||
SendAsync(command)).Should().ThrowAsync<ValidationException>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ShouldCreateTodoList()
|
||||
{
|
||||
var userId = await RunAsDefaultUserAsync();
|
||||
|
||||
var command = new CreateTodoListCommand
|
||||
{
|
||||
Title = "Tasks"
|
||||
};
|
||||
|
||||
var id = await SendAsync(command);
|
||||
|
||||
var list = await FindAsync<TodoList>(id);
|
||||
|
||||
list.Should().NotBeNull();
|
||||
list!.Title.Should().Be(command.Title);
|
||||
list.CreatedBy.Should().Be(userId);
|
||||
list.Created.Should().BeCloseTo(DateTime.Now, TimeSpan.FromMilliseconds(10000));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Hutopy.Application.TodoLists.Commands.CreateTodoList;
|
||||
using Hutopy.Application.TodoLists.Commands.DeleteTodoList;
|
||||
using Hutopy.Domain.Entities;
|
||||
|
||||
namespace Hutopy.Application.FunctionalTests.TodoLists.Commands;
|
||||
|
||||
using static Testing;
|
||||
|
||||
public class DeleteTodoListTests : BaseTestFixture
|
||||
{
|
||||
[Test]
|
||||
public async Task ShouldRequireValidTodoListId()
|
||||
{
|
||||
var command = new DeleteTodoListCommand(99);
|
||||
await FluentActions.Invoking(() => SendAsync(command)).Should().ThrowAsync<NotFoundException>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ShouldDeleteTodoList()
|
||||
{
|
||||
var listId = await SendAsync(new CreateTodoListCommand
|
||||
{
|
||||
Title = "New List"
|
||||
});
|
||||
|
||||
await SendAsync(new DeleteTodoListCommand(listId));
|
||||
|
||||
var list = await FindAsync<TodoList>(listId);
|
||||
|
||||
list.Should().BeNull();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using Hutopy.Application.Common.Exceptions;
|
||||
using Hutopy.Application.Common.Security;
|
||||
using Hutopy.Application.TodoLists.Commands.CreateTodoList;
|
||||
using Hutopy.Application.TodoLists.Commands.PurgeTodoLists;
|
||||
using Hutopy.Domain.Entities;
|
||||
|
||||
namespace Hutopy.Application.FunctionalTests.TodoLists.Commands;
|
||||
|
||||
using static Testing;
|
||||
|
||||
public class PurgeTodoListsTests : BaseTestFixture
|
||||
{
|
||||
[Test]
|
||||
public async Task ShouldDenyAnonymousUser()
|
||||
{
|
||||
var command = new PurgeTodoListsCommand();
|
||||
|
||||
command.GetType().Should().BeDecoratedWith<AuthorizeAttribute>();
|
||||
|
||||
var action = () => SendAsync(command);
|
||||
|
||||
await action.Should().ThrowAsync<UnauthorizedAccessException>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ShouldDenyNonAdministrator()
|
||||
{
|
||||
await RunAsDefaultUserAsync();
|
||||
|
||||
var command = new PurgeTodoListsCommand();
|
||||
|
||||
var action = () => SendAsync(command);
|
||||
|
||||
await action.Should().ThrowAsync<ForbiddenAccessException>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ShouldAllowAdministrator()
|
||||
{
|
||||
await RunAsAdministratorAsync();
|
||||
|
||||
var command = new PurgeTodoListsCommand();
|
||||
|
||||
var action = () => SendAsync(command);
|
||||
|
||||
await action.Should().NotThrowAsync<ForbiddenAccessException>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ShouldDeleteAllLists()
|
||||
{
|
||||
await RunAsAdministratorAsync();
|
||||
|
||||
await SendAsync(new CreateTodoListCommand
|
||||
{
|
||||
Title = "New List #1"
|
||||
});
|
||||
|
||||
await SendAsync(new CreateTodoListCommand
|
||||
{
|
||||
Title = "New List #2"
|
||||
});
|
||||
|
||||
await SendAsync(new CreateTodoListCommand
|
||||
{
|
||||
Title = "New List #3"
|
||||
});
|
||||
|
||||
await SendAsync(new PurgeTodoListsCommand());
|
||||
|
||||
var count = await CountAsync<TodoList>();
|
||||
|
||||
count.Should().Be(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using Hutopy.Application.Common.Exceptions;
|
||||
using Hutopy.Application.TodoLists.Commands.CreateTodoList;
|
||||
using Hutopy.Application.TodoLists.Commands.UpdateTodoList;
|
||||
using Hutopy.Domain.Entities;
|
||||
|
||||
namespace Hutopy.Application.FunctionalTests.TodoLists.Commands;
|
||||
|
||||
using static Testing;
|
||||
|
||||
public class UpdateTodoListTests : BaseTestFixture
|
||||
{
|
||||
[Test]
|
||||
public async Task ShouldRequireValidTodoListId()
|
||||
{
|
||||
var command = new UpdateTodoListCommand { Id = 99, Title = "New Title" };
|
||||
await FluentActions.Invoking(() => SendAsync(command)).Should().ThrowAsync<NotFoundException>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ShouldRequireUniqueTitle()
|
||||
{
|
||||
var listId = await SendAsync(new CreateTodoListCommand
|
||||
{
|
||||
Title = "New List"
|
||||
});
|
||||
|
||||
await SendAsync(new CreateTodoListCommand
|
||||
{
|
||||
Title = "Other List"
|
||||
});
|
||||
|
||||
var command = new UpdateTodoListCommand
|
||||
{
|
||||
Id = listId,
|
||||
Title = "Other List"
|
||||
};
|
||||
|
||||
(await FluentActions.Invoking(() =>
|
||||
SendAsync(command))
|
||||
.Should().ThrowAsync<ValidationException>().Where(ex => ex.Errors.ContainsKey("Title")))
|
||||
.And.Errors["Title"].Should().Contain("'Title' must be unique.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ShouldUpdateTodoList()
|
||||
{
|
||||
var userId = await RunAsDefaultUserAsync();
|
||||
|
||||
var listId = await SendAsync(new CreateTodoListCommand
|
||||
{
|
||||
Title = "New List"
|
||||
});
|
||||
|
||||
var command = new UpdateTodoListCommand
|
||||
{
|
||||
Id = listId,
|
||||
Title = "Updated List Title"
|
||||
};
|
||||
|
||||
await SendAsync(command);
|
||||
|
||||
var list = await FindAsync<TodoList>(listId);
|
||||
|
||||
list.Should().NotBeNull();
|
||||
list!.Title.Should().Be(command.Title);
|
||||
list.LastModifiedBy.Should().NotBeNull();
|
||||
list.LastModifiedBy.Should().Be(userId);
|
||||
list.LastModified.Should().BeCloseTo(DateTime.Now, TimeSpan.FromMilliseconds(10000));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using Hutopy.Application.TodoLists.Queries.GetTodos;
|
||||
using Hutopy.Domain.Entities;
|
||||
using Hutopy.Domain.ValueObjects;
|
||||
|
||||
namespace Hutopy.Application.FunctionalTests.TodoLists.Queries;
|
||||
|
||||
using static Testing;
|
||||
|
||||
public class GetTodosTests : BaseTestFixture
|
||||
{
|
||||
[Test]
|
||||
public async Task ShouldReturnPriorityLevels()
|
||||
{
|
||||
await RunAsDefaultUserAsync();
|
||||
|
||||
var query = new GetTodosQuery();
|
||||
|
||||
var result = await SendAsync(query);
|
||||
|
||||
result.PriorityLevels.Should().NotBeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ShouldReturnAllListsAndItems()
|
||||
{
|
||||
await RunAsDefaultUserAsync();
|
||||
|
||||
await AddAsync(new TodoList
|
||||
{
|
||||
Title = "Shopping",
|
||||
Colour = Colour.Blue,
|
||||
Items =
|
||||
{
|
||||
new TodoItem { Title = "Apples", Done = true },
|
||||
new TodoItem { Title = "Milk", Done = true },
|
||||
new TodoItem { Title = "Bread", Done = true },
|
||||
new TodoItem { Title = "Toilet paper" },
|
||||
new TodoItem { Title = "Pasta" },
|
||||
new TodoItem { Title = "Tissues" },
|
||||
new TodoItem { Title = "Tuna" }
|
||||
}
|
||||
});
|
||||
|
||||
var query = new GetTodosQuery();
|
||||
|
||||
var result = await SendAsync(query);
|
||||
|
||||
result.Lists.Should().HaveCount(1);
|
||||
result.Lists.First().Items.Should().HaveCount(7);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ShouldDenyAnonymousUser()
|
||||
{
|
||||
var query = new GetTodosQuery();
|
||||
|
||||
var action = () => SendAsync(query);
|
||||
|
||||
await action.Should().ThrowAsync<UnauthorizedAccessException>();
|
||||
}
|
||||
}
|
||||
5
tests/Application.FunctionalTests/appsettings.json
Normal file
5
tests/Application.FunctionalTests/appsettings.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=HutopyTestDb;Trusted_Connection=True;MultipleActiveResultSets=true"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user