diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/IntegrationTests/AuthorizedEndpointsIntegrationTests.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/IntegrationTests/AuthorizedEndpointsIntegrationTests.cs new file mode 100644 index 000000000000..6d8c72f78bd6 --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/IntegrationTests/AuthorizedEndpointsIntegrationTests.cs @@ -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> +{ + private readonly TestWebApplicationFactory _factory; + + public AuthorizedEndpointsIntegrationTests(TestWebApplicationFactory factory) + { + _factory = factory; + } + + public static IEnumerable AdminFlags => new List + { + 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("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); + } +} diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/IntegrationTests/Helpers/TestAuthHandler.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/IntegrationTests/Helpers/TestAuthHandler.cs new file mode 100644 index 000000000000..b2ec0fe37e5c --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/IntegrationTests/Helpers/TestAuthHandler.cs @@ -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 +{ + public TestAuthHandler(IOptionsMonitor options, + ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) + : base(options, logger, encoder, clock) + { + } + + protected override Task 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); + } +} diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/IntegrationTests/Helpers/TestEmailService.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/IntegrationTests/Helpers/TestEmailService.cs new file mode 100644 index 000000000000..09005956ab1d --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/IntegrationTests/Helpers/TestEmailService.cs @@ -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; + } +} diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/IntegrationTests/Helpers/TestWebApplicationFactory.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/IntegrationTests/Helpers/TestWebApplicationFactory.cs new file mode 100644 index 000000000000..8671dd103fc2 --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/IntegrationTests/Helpers/TestWebApplicationFactory.cs @@ -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 + : WebApplicationFactory where TProgram : class +{ + protected override IHost CreateHost(IHostBuilder builder) + { + builder.ConfigureServices(services => + { + var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions)); + + if (descriptor != null) + { + services.Remove(descriptor); + } + + services.AddDbContext(options => + { + var path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + options.UseSqlite($"Data Source={Path.Join(path, "WebMinRouteGroup_tests.db")}"); + }); + }); + + return base.CreateHost(builder); + } +} diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/IntegrationTests/TodoIntegrationTestsV1.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/IntegrationTests/TodoIntegrationTestsV1.cs new file mode 100644 index 000000000000..3dfef16a4bee --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/IntegrationTests/TodoIntegrationTestsV1.cs @@ -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> +{ + private readonly TestWebApplicationFactory _factory; + private readonly HttpClient _httpClient; + + public TodoIntegrationTestsV1(TestWebApplicationFactory factory) + { + _factory = factory; + _httpClient = factory.CreateClient(); + } + + public static IEnumerable InvalidTodos => new List + { + 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(); + + 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(); + 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>("/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); + }); + } +} diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/IntegrationTests/TodoIntegrationTestsV2.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/IntegrationTests/TodoIntegrationTestsV2.cs new file mode 100644 index 000000000000..cbdf744286d5 --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/IntegrationTests/TodoIntegrationTestsV2.cs @@ -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> +{ + private readonly TestWebApplicationFactory _factory; + private readonly HttpClient _httpClient; + + public TodoIntegrationTestsV2(TestWebApplicationFactory factory) + { + _factory = factory; + _httpClient = factory.CreateClient(); + } + + public static IEnumerable InvalidTodos => new List + { + 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(); + + 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(); + if (db != null && db.Todos.Any()) + { + db.Todos.RemoveRange(db.Todos); + await db.SaveChangesAsync(); + } + } + + var client = _factory.WithWebHostBuilder(builder => + { + builder.ConfigureTestServices(services => + { + services.AddSingleton(); + }); + }).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>("/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); + }); + } +} diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/MinApiTests.csproj b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/MinApiTests.csproj new file mode 100644 index 000000000000..a4ba89b23392 --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/MinApiTests.csproj @@ -0,0 +1,31 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/UnitTests/Helpers/MockDb.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/UnitTests/Helpers/MockDb.cs new file mode 100644 index 000000000000..1d086fc608ac --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/UnitTests/Helpers/MockDb.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using WebMinRouteGroup.Data; + +namespace MinApiTests.UnitTests.Helpers; + +public class MockDb : IDbContextFactory +{ + public TodoGroupDbContext CreateDbContext() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase($"InMemoryTestDb-{DateTime.Now.ToFileTimeUtc()}") + .Options; + + return new TodoGroupDbContext(options); + } +} diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/UnitTests/TodoInMemoryTests.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/UnitTests/TodoInMemoryTests.cs new file mode 100644 index 000000000000..2710882a474d --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/UnitTests/TodoInMemoryTests.cs @@ -0,0 +1,184 @@ +using Microsoft.AspNetCore.Http.HttpResults; +using MinApiTests.UnitTests.Helpers; +using WebMinRouteGroup; +using WebMinRouteGroup.Data; + +namespace MinApiTests.UnitTests; + +public class TodoInMemoryTests +{ + [Fact] + public async Task GetTodoReturnsNotFoundIfNotExists() + { + // Arrange + await using var context = new MockDb().CreateDbContext(); + + // Act + var notFoundResult = (NotFound)await TodoEndpointsV1.GetTodo(404, context); + + //Assert + Assert.Equal(404, notFoundResult.StatusCode); + } + + [Fact] + public async Task GetAllReturnsTodosFromDatabase() + { + // Arrange + await using var context = new MockDb().CreateDbContext(); + + context.Todos.Add(new Todo + { + Id = 1, + Title = "Test title 1", + Description = "Test description 1", + IsDone = false + }); + + context.Todos.Add(new Todo + { + Id = 2, + Title = "Test title 2", + Description = "Test description 2", + IsDone = true + }); + + await context.SaveChangesAsync(); + + // Act + var okResult = (Ok>)await TodoEndpointsV1.GetAllTodos(context); + + //Assert + Assert.Equal(200, okResult.StatusCode); + var foundTodos = Assert.IsAssignableFrom>(okResult.Value); + + Assert.NotEmpty(foundTodos); + Assert.Collection(foundTodos, todo1 => + { + Assert.Equal("Test title 1", todo1.Title); + Assert.Equal("Test description 1", todo1.Description); + Assert.False(todo1.IsDone); + }, todo2 => + { + Assert.Equal("Test title 2", todo2.Title); + Assert.Equal("Test description 2", todo2.Description); + Assert.True(todo2.IsDone); + }); + } + + [Fact] + public async Task GetTodoReturnsTodoFromDatabase() + { + // Arrange + await using var context = new MockDb().CreateDbContext(); + + context.Todos.Add(new Todo + { + Id = 1, + Title = "Test title", + Description = "Test description", + IsDone = false + }); + + await context.SaveChangesAsync(); + + // Act + var okResult = (Ok)await TodoEndpointsV1.GetTodo(1, context); + + //Assert + Assert.Equal(200, okResult.StatusCode); + var foundTodo = Assert.IsAssignableFrom(okResult.Value); + Assert.Equal(1, foundTodo.Id); + } + + [Fact] + public async Task CreateTodoCreatesTodoInDatabase() + { + //Arrange + await using var context = new MockDb().CreateDbContext(); + + var newTodo = new TodoDto + { + Title = "Test title", + Description = "Test description", + IsDone = false + }; + + //Act + var createdResult = (Created)await TodoEndpointsV1.CreateTodo(newTodo, context); + + //Assert + Assert.Equal(201, createdResult.StatusCode); + Assert.NotNull(createdResult.Location); + Assert.IsAssignableFrom(createdResult.Value); + + Assert.NotEmpty(context.Todos); + Assert.Collection(context.Todos, todo => + { + Assert.Equal("Test title", todo.Title); + Assert.Equal("Test description", todo.Description); + Assert.False(todo.IsDone); + }); + } + + [Fact] + public async Task UpdateTodoUpdatesTodoInDatabase() + { + //Arrange + await using var context = new MockDb().CreateDbContext(); + + context.Todos.Add(new Todo + { + Id = 1, + Title = "Exiting test title", + IsDone = false + }); + + await context.SaveChangesAsync(); + + var updatedTodo = new Todo + { + Id = 1, + Title = "Updated test title", + IsDone = true + }; + + //Act + var createdResult = (Created)await TodoEndpointsV1.UpdateTodo(updatedTodo, context); + + //Assert + Assert.Equal(201, createdResult.StatusCode); + Assert.NotNull(createdResult.Location); + Assert.IsAssignableFrom(createdResult.Value); + + var todoInDb = await context.Todos.FindAsync(1); + + Assert.NotNull(todoInDb); + Assert.Equal("Updated test title", todoInDb!.Title); + Assert.True(todoInDb.IsDone); + } + + [Fact] + public async Task DeleteTodoDeletesTodoInDatabase() + { + //Arrange + await using var context = new MockDb().CreateDbContext(); + + var existingTodo = new Todo + { + Id = 1, + Title = "Exiting test title", + IsDone = false + }; + + context.Todos.Add(existingTodo); + + await context.SaveChangesAsync(); + + //Act + var noContentResult = (NoContent)await TodoEndpointsV1.DeleteTodo(existingTodo.Id, context); + + //Assert + Assert.Equal(204, noContentResult.StatusCode); + Assert.Empty(context.Todos); + } +} diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/UnitTests/TodoMoqTests.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/UnitTests/TodoMoqTests.cs new file mode 100644 index 000000000000..d3075b0afa55 --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/UnitTests/TodoMoqTests.cs @@ -0,0 +1,240 @@ +using Microsoft.AspNetCore.Http.HttpResults; +using Moq; +using WebMinRouteGroup; +using WebMinRouteGroup.Data; +using WebMinRouteGroup.Services; + +namespace MinApiTests.UnitTests; + +public class TodoMoqTests +{ + [Fact] + public async Task GetTodoReturnsNotFoundIfNotExists() + { + // Arrange + var mock = new Mock(); + + mock.Setup(m => m.Find(It.Is(id => id == 404))) + .ReturnsAsync((Todo?)null); + + // Act + var notFoundResult = (NotFound)await TodoEndpointsV2.GetTodo(404, mock.Object); + + //Assert + Assert.Equal(404, notFoundResult.StatusCode); + } + + [Fact] + public async Task GetAllReturnsTodosFromDatabase() + { + // Arrange + var mock = new Mock(); + + mock.Setup(m => m.GetAll()) + .ReturnsAsync(new List { + new Todo + { + Id = 1, + Title = "Test title 1", + IsDone = false + }, + new Todo + { + Id = 2, + Title = "Test title 2", + IsDone = true + } + }); + + // Act + var okResult = (Ok>)await TodoEndpointsV2.GetAllTodos(mock.Object); + + //Assert + Assert.Equal(200, okResult.StatusCode); + var foundTodos = Assert.IsAssignableFrom>(okResult.Value); + + Assert.NotEmpty(foundTodos); + Assert.Collection(foundTodos, todo1 => + { + Assert.Equal(1, todo1.Id); + Assert.Equal("Test title 1", todo1.Title); + Assert.False(todo1.IsDone); + }, todo2 => + { + Assert.Equal(2, todo2.Id); + Assert.Equal("Test title 2", todo2.Title); + Assert.True(todo2.IsDone); + }); + } + + [Fact] + public async Task GetAllIncompletedReturnsIncompletedTodosFromDatabase() + { + // Arrange + var mock = new Mock(); + + mock.Setup(m => m.GetIncompleteTodos()) + .ReturnsAsync(new List { + new Todo + { + Id = 1, + Title = "Test title 1", + IsDone = false + }, + new Todo + { + Id = 2, + Title = "Test title 2", + IsDone = false + } + }); + + // Act + var okResult = (Ok>)await TodoEndpointsV2.GetAllIncompletedTodos(mock.Object); + + //Assert + Assert.Equal(200, okResult.StatusCode); + var foundTodos = Assert.IsAssignableFrom>(okResult.Value); + + Assert.NotEmpty(foundTodos); + Assert.Collection(foundTodos, todo1 => + { + Assert.Equal(1, todo1.Id); + Assert.Equal("Test title 1", todo1.Title); + Assert.False(todo1.IsDone); + }, todo2 => + { + Assert.Equal(2, todo2.Id); + Assert.Equal("Test title 2", todo2.Title); + Assert.False(todo2.IsDone); + }); + } + + [Fact] + public async Task GetTodoReturnsTodoFromDatabase() + { + // Arrange + var mock = new Mock(); + + mock.Setup(m => m.Find(It.Is(id => id == 1))) + .ReturnsAsync(new Todo + { + Id = 1, + Title = "Test title", + IsDone = false + }); + + // Act + var okResult = (Ok)await TodoEndpointsV2.GetTodo(1, mock.Object); + + //Assert + Assert.Equal(200, okResult.StatusCode); + var foundTodo = Assert.IsAssignableFrom(okResult.Value); + Assert.Equal(1, foundTodo.Id); + } + + [Fact] + public async Task CreateTodoCreatesTodoInDatabase() + { + //Arrange + var todos = new List(); + + var newTodo = new TodoDto + { + Title = "Test title", + Description = "Test description", + IsDone = false + }; + + var mock = new Mock(); + + mock.Setup(m => m.Add(It.Is(t => t.Title == newTodo.Title && t.Description == newTodo.Description && t.IsDone == newTodo.IsDone))) + .Callback(todo => todos.Add(todo)) + .Returns(Task.CompletedTask); + + //Act + var createdResult = (Created)await TodoEndpointsV2.CreateTodo(newTodo, mock.Object); + + //Assert + Assert.Equal(201, createdResult.StatusCode); + Assert.NotNull(createdResult.Location); + Assert.IsAssignableFrom(createdResult.Value); + + Assert.NotEmpty(todos); + Assert.Collection(todos, todo => + { + Assert.Equal("Test title", todo.Title); + Assert.Equal("Test description", todo.Description); + Assert.False(todo.IsDone); + }); + } + + [Fact] + public async Task UpdateTodoUpdatesTodoInDatabase() + { + //Arrange + var existingTodo = new Todo + { + Id = 1, + Title = "Exiting test title", + IsDone = false + }; + + var updatedTodo = new Todo + { + Id = 1, + Title = "Updated test title", + IsDone = true + }; + + var mock = new Mock(); + + mock.Setup(m => m.Find(It.Is(id => id == 1))) + .ReturnsAsync(existingTodo); + + mock.Setup(m => m.Update(It.Is(t => t.Id == updatedTodo.Id && t.Description == updatedTodo.Description && t.IsDone == updatedTodo.IsDone))) + .Callback(todo => existingTodo = todo) + .Returns(Task.CompletedTask); + + //Act + var createdResult = (Created)await TodoEndpointsV2.UpdateTodo(updatedTodo, mock.Object); + + //Assert + Assert.Equal(201, createdResult.StatusCode); + Assert.NotNull(createdResult.Location); + Assert.IsAssignableFrom(createdResult.Value); + + Assert.Equal("Updated test title", existingTodo.Title); + Assert.True(existingTodo.IsDone); + } + + [Fact] + public async Task DeleteTodoDeletesTodoInDatabase() + { + //Arrange + var existingTodo = new Todo + { + Id = 1, + Title = "Test title 1", + IsDone = false + }; + + var todos = new List { existingTodo }; + + var mock = new Mock(); + + mock.Setup(m => m.Find(It.Is(id => id == existingTodo.Id))) + .ReturnsAsync(existingTodo); + + mock.Setup(m => m.Remove(It.Is(t => t.Id == 1))) + .Callback(t => todos.Remove(t)) + .Returns(Task.CompletedTask); + + //Act + var noContentResult = (NoContent)await TodoEndpointsV2.DeleteTodo(existingTodo.Id, mock.Object); + + //Assert + Assert.Equal(204, noContentResult.StatusCode); + Assert.Empty(todos); + } +} diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/Usings.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/Usings.cs new file mode 100644 index 000000000000..8c927eb747a6 --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTests/Usings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTestsSample.sln b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTestsSample.sln new file mode 100644 index 000000000000..e59a954f3fa5 --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/MinApiTestsSample.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebMinRouteGroup", "WebMinRouteGroup\WebMinRouteGroup.csproj", "{3256AE2D-3E23-4869-B914-51A04C2357CF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MinApiTests", "MinApiTests\MinApiTests.csproj", "{2B14F621-3DC6-40D5-87F5-469CDE54F788}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3256AE2D-3E23-4869-B914-51A04C2357CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3256AE2D-3E23-4869-B914-51A04C2357CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3256AE2D-3E23-4869-B914-51A04C2357CF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3256AE2D-3E23-4869-B914-51A04C2357CF}.Release|Any CPU.Build.0 = Release|Any CPU + {2B14F621-3DC6-40D5-87F5-469CDE54F788}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B14F621-3DC6-40D5-87F5-469CDE54F788}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B14F621-3DC6-40D5-87F5-469CDE54F788}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B14F621-3DC6-40D5-87F5-469CDE54F788}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {47B1B0C6-1AC8-4B66-84E5-6F4A09F2E1BB} + EndGlobalSection +EndGlobal diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Data/Todo.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Data/Todo.cs new file mode 100644 index 000000000000..9be2d443790e --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Data/Todo.cs @@ -0,0 +1,9 @@ +namespace WebMinRouteGroup.Data; + +public class Todo +{ + public int Id { get; set; } + public string Title { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public bool IsDone { get; set; } +} diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Data/TodoDto.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Data/TodoDto.cs new file mode 100644 index 000000000000..eda4c49b45c6 --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Data/TodoDto.cs @@ -0,0 +1,8 @@ +namespace WebMinRouteGroup.Data; + +public class TodoDto +{ + public string Title { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public bool IsDone { get; set; } +} diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Data/TodoGroupDbContext.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Data/TodoGroupDbContext.cs new file mode 100644 index 000000000000..64707db7ff35 --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Data/TodoGroupDbContext.cs @@ -0,0 +1,13 @@ +using Microsoft.EntityFrameworkCore; + +namespace WebMinRouteGroup.Data; + +public class TodoGroupDbContext : DbContext +{ + public DbSet Todos => Set(); + + public TodoGroupDbContext(DbContextOptions options) + : base(options) + { + } +} diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Migrations/20220821140431_InitialCreate.Designer.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Migrations/20220821140431_InitialCreate.Designer.cs new file mode 100644 index 000000000000..af49cd4a19de --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Migrations/20220821140431_InitialCreate.Designer.cs @@ -0,0 +1,46 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using WebMinRouteGroup.Data; + +#nullable disable + +namespace WebMinRouteGroup.Migrations +{ + [DbContext(typeof(TodoGroupDbContext))] + [Migration("20220821140431_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.0-preview.7.22376.2"); + + modelBuilder.Entity("WebMinRouteGroup.Data.Todo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsDone") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Todos"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Migrations/20220821140431_InitialCreate.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Migrations/20220821140431_InitialCreate.cs new file mode 100644 index 000000000000..48ec7a0def7e --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Migrations/20220821140431_InitialCreate.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace WebMinRouteGroup.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Todos", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Title = table.Column(type: "TEXT", nullable: false), + Description = table.Column(type: "TEXT", nullable: false), + IsDone = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Todos", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Todos"); + } + } +} diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Migrations/TodoGroupDbContextModelSnapshot.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Migrations/TodoGroupDbContextModelSnapshot.cs new file mode 100644 index 000000000000..ae12297c8c42 --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Migrations/TodoGroupDbContextModelSnapshot.cs @@ -0,0 +1,43 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using WebMinRouteGroup.Data; + +#nullable disable + +namespace WebMinRouteGroup.Migrations +{ + [DbContext(typeof(TodoGroupDbContext))] + partial class TodoGroupDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.0-preview.7.22376.2"); + + modelBuilder.Entity("WebMinRouteGroup.Data.Todo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsDone") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Todos"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Program.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Program.cs new file mode 100644 index 000000000000..9cb7fd7f3af1 --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Program.cs @@ -0,0 +1,54 @@ +using Microsoft.EntityFrameworkCore; +using WebMinRouteGroup; +using WebMinRouteGroup.Data; +using WebMinRouteGroup.Services; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly", + b => b.RequireClaim("admin", "true"))); + +builder.Services.AddTransient(); +builder.Services.AddSingleton(); + +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddDbContext(options => +{ + var path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + options.UseSqlite($"Data Source={Path.Join(path, "WebMinRouteGroup.db")}"); +}); + +var app = builder.Build(); + +using var scope = app.Services.CreateScope(); +var db = scope.ServiceProvider.GetService(); +db?.Database.MigrateAsync(); + +app.UseAuthorization(); + +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.MapGet("/", () => "Hello World!"); + +app.MapGet("/admin", () => "Authorized Endpoint") + .RequireAuthorization("AdminsOnly"); + +// todoV1 endpoints +app.MapGroup("/todos/v1") + .MapTodosApiV1() + .WithTags("Todo Endpoints"); + +// todoV2 endpoints +app.MapGroup("/todos/v2") + .MapTodosApiV2() + .WithTags("Todo Endpoints"); + +app.Run(); + +public partial class Program +{ } diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Services/EmailService.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Services/EmailService.cs new file mode 100644 index 000000000000..4380edecd61d --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Services/EmailService.cs @@ -0,0 +1,10 @@ +namespace WebMinRouteGroup.Services; + +public class EmailService : IEmailService +{ + public Task Send(string emailAddress, string body) + { + // Code for sending mails to a configured host + return Task.CompletedTask; + } +} diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Services/IEmailService.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Services/IEmailService.cs new file mode 100644 index 000000000000..55bf3b1d8e6f --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Services/IEmailService.cs @@ -0,0 +1,6 @@ +namespace WebMinRouteGroup.Services; + +public interface IEmailService +{ + Task Send(string emailAddress, string body); +} diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Services/ITodoService.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Services/ITodoService.cs new file mode 100644 index 000000000000..cea30efe735f --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Services/ITodoService.cs @@ -0,0 +1,18 @@ +using WebMinRouteGroup.Data; + +namespace WebMinRouteGroup.Services; + +public interface ITodoService +{ + Task> GetAll(); + + Task> GetIncompleteTodos(); + + ValueTask Find(int id); + + Task Add(Todo todo); + + Task Update(Todo todo); + + Task Remove(Todo todo); +} diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Services/TodoService.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Services/TodoService.cs new file mode 100644 index 000000000000..54689cdb422c --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Services/TodoService.cs @@ -0,0 +1,51 @@ +using Microsoft.EntityFrameworkCore; +using WebMinRouteGroup.Data; + +namespace WebMinRouteGroup.Services; + +public class TodoService : ITodoService +{ + private readonly TodoGroupDbContext _dbContext; + private readonly IEmailService _emailService; + + public TodoService(TodoGroupDbContext dbContext, IEmailService emailService) + { + _dbContext = dbContext; + _emailService = emailService; + } + + public async ValueTask Find(int id) + { + return await _dbContext.Todos.FindAsync(id); + } + + public async Task> GetAll() + { + return await _dbContext.Todos.ToListAsync(); + } + + public async Task Add(Todo todo) + { + await _dbContext.Todos.AddAsync(todo); + + if (await _dbContext.SaveChangesAsync() > 0) + await _emailService.Send("hello@microsoft.com", $"New todo has been added: {todo.Title}"); + } + + public async Task Update(Todo todo) + { + _dbContext.Todos.Update(todo); + await _dbContext.SaveChangesAsync(); + } + + public async Task Remove(Todo todo) + { + _dbContext.Todos.Remove(todo); + await _dbContext.SaveChangesAsync(); + } + + public Task> GetIncompleteTodos() + { + return _dbContext.Todos.Where(t => t.IsDone == false).ToListAsync(); + } +} diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/TodoEndpointsV1.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/TodoEndpointsV1.cs new file mode 100644 index 000000000000..a03b8b24749a --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/TodoEndpointsV1.cs @@ -0,0 +1,103 @@ +using Microsoft.EntityFrameworkCore; +using WebMinRouteGroup.Data; + +namespace WebMinRouteGroup; + +public static class TodoEndpointsV1 +{ + public static RouteGroupBuilder MapTodosApiV1(this RouteGroupBuilder group) + { + group.MapGet("/", GetAllTodos); + group.MapGet("/{id}", GetTodo); + group.MapPost("/", CreateTodo) + .AddEndpointFilter(async (efiContext, next) => + { + var param = efiContext.GetArgument(0); + + var validationErrors = Utilities.IsValid(param); + + if (validationErrors.Any()) + { + return Results.ValidationProblem(validationErrors); + } + + return await next(efiContext); + }); + + group.MapPut("/{id}", UpdateTodo); + group.MapDelete("/{id}", DeleteTodo); + + return group; + } + + // get all todos + public static async Task GetAllTodos(TodoGroupDbContext database) + { + var todos = await database.Todos.ToListAsync(); + return TypedResults.Ok(todos); + } + + // get todo by id + public static async Task GetTodo(int id, TodoGroupDbContext database) + { + var todo = await database.Todos.FindAsync(id); + + if (todo != null) + { + return TypedResults.Ok(todo); + } + + return TypedResults.NotFound(); + } + + // create todo + public static async Task CreateTodo(TodoDto todo, TodoGroupDbContext database) + { + var newTodo = new Todo + { + Title = todo.Title, + Description = todo.Description, + IsDone = todo.IsDone + }; + + await database.Todos.AddAsync(newTodo); + await database.SaveChangesAsync(); + + return TypedResults.Created($"/todos/v1/{newTodo.Id}", newTodo); + } + + // update todo + public static async Task UpdateTodo(Todo todo, TodoGroupDbContext database) + { + var existingTodo = await database.Todos.FindAsync(todo.Id); + + if (existingTodo != null) + { + existingTodo.Title = todo.Title; + existingTodo.Description = todo.Description; + existingTodo.IsDone = todo.IsDone; + + await database.SaveChangesAsync(); + + return TypedResults.Created($"/todos/v1/{existingTodo.Id}", existingTodo); + } + + return TypedResults.NotFound(); + } + + // delete todo + public static async Task DeleteTodo(int id, TodoGroupDbContext database) + { + var todo = await database.Todos.FindAsync(id); + + if (todo != null) + { + database.Todos.Remove(todo); + await database.SaveChangesAsync(); + + return TypedResults.NoContent(); + } + + return TypedResults.NotFound(); + } +} diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/TodoEndpointsV2.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/TodoEndpointsV2.cs new file mode 100644 index 000000000000..30c3e5ee9f23 --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/TodoEndpointsV2.cs @@ -0,0 +1,108 @@ +using WebMinRouteGroup.Data; +using WebMinRouteGroup.Services; + +namespace WebMinRouteGroup; + +public static class TodoEndpointsV2 +{ + public static RouteGroupBuilder MapTodosApiV2(this RouteGroupBuilder group) + { + group.MapGet("/", GetAllTodos); + group.MapGet("/incompleted", GetAllIncompletedTodos); + group.MapGet("/{id}", GetTodo); + + group.MapPost("/", CreateTodo) + .AddEndpointFilter(async (efiContext, next) => + { + var param = efiContext.GetArgument(0); + + var validationErrors = Utilities.IsValid(param); + + if (validationErrors.Any()) + { + return Results.ValidationProblem(validationErrors); + } + + return await next(efiContext); + }); + + group.MapPut("/{id}", UpdateTodo); + group.MapDelete("/{id}", DeleteTodo); + + return group; + } + + // get all todos + public static async Task GetAllTodos(ITodoService todoService) + { + var todos = await todoService.GetAll(); + return TypedResults.Ok(todos); + } + + public static async Task GetAllIncompletedTodos(ITodoService todoService) + { + var todos = await todoService.GetIncompleteTodos(); + return TypedResults.Ok(todos); + } + + // get todo by id + public static async Task GetTodo(int id, ITodoService todoService) + { + var todo = await todoService.Find(id); + + if (todo != null) + { + return TypedResults.Ok(todo); + } + + return TypedResults.NotFound(); + } + + // create todo + public static async Task CreateTodo(TodoDto todo, ITodoService todoService) + { + var newTodo = new Todo + { + Title = todo.Title, + Description = todo.Description, + IsDone = todo.IsDone + }; + + await todoService.Add(newTodo); + + return TypedResults.Created($"/todos/v1/{newTodo.Id}", newTodo); + } + + // update todo + public static async Task UpdateTodo(Todo todo, ITodoService todoService) + { + var existingTodo = await todoService.Find(todo.Id); + + if (existingTodo != null) + { + existingTodo.Title = todo.Title; + existingTodo.Description = todo.Description; + existingTodo.IsDone = todo.IsDone; + + await todoService.Update(existingTodo); + + return TypedResults.Created($"/todos/v1/{existingTodo.Id}", existingTodo); + } + + return TypedResults.NotFound(); + } + + // delete todo + public static async Task DeleteTodo(int id, ITodoService todoService) + { + var todo = await todoService.Find(id); + + if (todo != null) + { + await todoService.Remove(todo); + return TypedResults.NoContent(); + } + + return TypedResults.NotFound(); + } +} diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Utilities.cs b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Utilities.cs new file mode 100644 index 000000000000..b1705011f700 --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/Utilities.cs @@ -0,0 +1,23 @@ +using WebMinRouteGroup.Data; + +namespace WebMinRouteGroup; + +public static class Utilities +{ + public static Dictionary IsValid(TodoDto td) + { + Dictionary errors = new(); + + if (string.IsNullOrEmpty(td.Title)) + { + errors.TryAdd("todo.name.errors", new[] { "Name is empty" }); + } + + if (td.Title.Length < 3) + { + errors.TryAdd("todo.name.errors", new[] { "Name length < 3" }); + } + + return errors; + } +} diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/WebMinRouteGroup.csproj b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/WebMinRouteGroup.csproj new file mode 100644 index 000000000000..04fab39ba53d --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/WebMinRouteGroup.csproj @@ -0,0 +1,24 @@ + + + + net7.0 + enable + enable + WebMinRouteGroup + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/appsettings.Development.json b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/appsettings.Development.json new file mode 100644 index 000000000000..0c208ae9181e --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/appsettings.json b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/appsettings.json new file mode 100644 index 000000000000..10f68b8c8b4f --- /dev/null +++ b/aspnetcore/fundamentals/minimal-apis/7.0-samples/MinApiTestsSample/WebMinRouteGroup/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +}