From 1897428d001cc6125532b792b2e57a7179139c14 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Sun, 24 Nov 2024 10:42:33 +0100 Subject: [PATCH 01/12] Reorganized project in solution folders --- HopFrame.sln | 17 +++++++++++++++-- {test => testing}/FrontendTest/.gitignore | 0 {test => testing}/FrontendTest/AdminContext.cs | 0 .../FrontendTest/Components/App.razor | 0 .../Components/Layout/MainLayout.razor | 0 .../Components/Layout/MainLayout.razor.css | 0 .../Components/Layout/NavMenu.razor | 0 .../Components/Layout/NavMenu.razor.css | 0 .../Components/Pages/Counter.razor | 0 .../FrontendTest/Components/Pages/Error.razor | 0 .../FrontendTest/Components/Pages/Home.razor | 0 .../Components/Pages/Weather.razor | 0 .../FrontendTest/Components/Routes.razor | 0 .../FrontendTest/Components/_Imports.razor | 0 .../FrontendTest/DatabaseContext.cs | 2 +- .../FrontendTest/FrontendTest.csproj | 0 .../FrontendTest/Models/Address.cs | 0 .../FrontendTest/Models/Employee.cs | 0 {test => testing}/FrontendTest/Program.cs | 0 .../Properties/launchSettings.json | 0 .../FrontendTest/Providers/AddressProvider.cs | 0 .../FrontendTest/Providers/EmployeeProvider.cs | 0 .../FrontendTest/appsettings.json | 0 {test => testing}/FrontendTest/wwwroot/app.css | 0 .../FrontendTest/wwwroot/favicon.png | Bin {test => testing}/RestApiTest/.gitignore | 0 .../RestApiTest/Controllers/TestController.cs | 0 .../RestApiTest/DatabaseContext.cs | 2 +- .../RestApiTest/Models/Address.cs | 0 .../RestApiTest/Models/Employee.cs | 0 {test => testing}/RestApiTest/Program.cs | 0 .../RestApiTest/Properties/launchSettings.json | 0 .../RestApiTest/RestApiTest.csproj | 0 {test => testing}/RestApiTest/appsettings.json | 0 34 files changed, 17 insertions(+), 4 deletions(-) rename {test => testing}/FrontendTest/.gitignore (100%) rename {test => testing}/FrontendTest/AdminContext.cs (100%) rename {test => testing}/FrontendTest/Components/App.razor (100%) rename {test => testing}/FrontendTest/Components/Layout/MainLayout.razor (100%) rename {test => testing}/FrontendTest/Components/Layout/MainLayout.razor.css (100%) rename {test => testing}/FrontendTest/Components/Layout/NavMenu.razor (100%) rename {test => testing}/FrontendTest/Components/Layout/NavMenu.razor.css (100%) rename {test => testing}/FrontendTest/Components/Pages/Counter.razor (100%) rename {test => testing}/FrontendTest/Components/Pages/Error.razor (100%) rename {test => testing}/FrontendTest/Components/Pages/Home.razor (100%) rename {test => testing}/FrontendTest/Components/Pages/Weather.razor (100%) rename {test => testing}/FrontendTest/Components/Routes.razor (100%) rename {test => testing}/FrontendTest/Components/_Imports.razor (100%) rename {test => testing}/FrontendTest/DatabaseContext.cs (88%) rename {test => testing}/FrontendTest/FrontendTest.csproj (100%) rename {test => testing}/FrontendTest/Models/Address.cs (100%) rename {test => testing}/FrontendTest/Models/Employee.cs (100%) rename {test => testing}/FrontendTest/Program.cs (100%) rename {test => testing}/FrontendTest/Properties/launchSettings.json (100%) rename {test => testing}/FrontendTest/Providers/AddressProvider.cs (100%) rename {test => testing}/FrontendTest/Providers/EmployeeProvider.cs (100%) rename {test => testing}/FrontendTest/appsettings.json (100%) rename {test => testing}/FrontendTest/wwwroot/app.css (100%) rename {test => testing}/FrontendTest/wwwroot/favicon.png (100%) rename {test => testing}/RestApiTest/.gitignore (100%) rename {test => testing}/RestApiTest/Controllers/TestController.cs (100%) rename {test => testing}/RestApiTest/DatabaseContext.cs (88%) rename {test => testing}/RestApiTest/Models/Address.cs (100%) rename {test => testing}/RestApiTest/Models/Employee.cs (100%) rename {test => testing}/RestApiTest/Program.cs (100%) rename {test => testing}/RestApiTest/Properties/launchSettings.json (100%) rename {test => testing}/RestApiTest/RestApiTest.csproj (100%) rename {test => testing}/RestApiTest/appsettings.json (100%) diff --git a/HopFrame.sln b/HopFrame.sln index 2e65007..269b7be 100644 --- a/HopFrame.sln +++ b/HopFrame.sln @@ -2,7 +2,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Database", "src\HopFrame.Database\HopFrame.Database.csproj", "{003120AE-F38B-4632-8497-BE4505189627}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestApiTest", "test\RestApiTest\RestApiTest.csproj", "{921159CE-AF75-44C3-A3F9-6B9B1A4E85CF}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestApiTest", "testing\RestApiTest\RestApiTest.csproj", "{921159CE-AF75-44C3-A3F9-6B9B1A4E85CF}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Security", "src\HopFrame.Security\HopFrame.Security.csproj", "{7F82E1C6-4A42-4337-9E03-2EE6429D004F}" EndProject @@ -10,10 +10,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Api", "src\HopFram EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Web", "src\HopFrame.Web\HopFrame.Web.csproj", "{3BE585BC-13A5-4BE4-A806-E9EC2D825956}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrontendTest", "test\FrontendTest\FrontendTest.csproj", "{8F983A37-63CF-48D5-988D-58B78EF8AECD}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrontendTest", "testing\FrontendTest\FrontendTest.csproj", "{8F983A37-63CF-48D5-988D-58B78EF8AECD}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Web.Admin", "src\HopFrame.Web.Admin\HopFrame.Web.Admin.csproj", "{02D9F10A-664A-4EF7-BF19-310C26FF4DEB}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{64EDCBED-A84F-4936-8697-78DC43CB2427}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Testing", "Testing", "{EEA20D27-D471-44AF-A287-C0E068D93182}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{1D98E5DE-CB8B-4C1C-A319-D49AC137441A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -50,5 +56,12 @@ Global {02D9F10A-664A-4EF7-BF19-310C26FF4DEB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution + {1E821490-AEDC-4F55-B758-52F4FADAB53A} = {64EDCBED-A84F-4936-8697-78DC43CB2427} + {003120AE-F38B-4632-8497-BE4505189627} = {64EDCBED-A84F-4936-8697-78DC43CB2427} + {7F82E1C6-4A42-4337-9E03-2EE6429D004F} = {64EDCBED-A84F-4936-8697-78DC43CB2427} + {3BE585BC-13A5-4BE4-A806-E9EC2D825956} = {64EDCBED-A84F-4936-8697-78DC43CB2427} + {02D9F10A-664A-4EF7-BF19-310C26FF4DEB} = {64EDCBED-A84F-4936-8697-78DC43CB2427} + {8F983A37-63CF-48D5-988D-58B78EF8AECD} = {EEA20D27-D471-44AF-A287-C0E068D93182} + {921159CE-AF75-44C3-A3F9-6B9B1A4E85CF} = {EEA20D27-D471-44AF-A287-C0E068D93182} EndGlobalSection EndGlobal diff --git a/test/FrontendTest/.gitignore b/testing/FrontendTest/.gitignore similarity index 100% rename from test/FrontendTest/.gitignore rename to testing/FrontendTest/.gitignore diff --git a/test/FrontendTest/AdminContext.cs b/testing/FrontendTest/AdminContext.cs similarity index 100% rename from test/FrontendTest/AdminContext.cs rename to testing/FrontendTest/AdminContext.cs diff --git a/test/FrontendTest/Components/App.razor b/testing/FrontendTest/Components/App.razor similarity index 100% rename from test/FrontendTest/Components/App.razor rename to testing/FrontendTest/Components/App.razor diff --git a/test/FrontendTest/Components/Layout/MainLayout.razor b/testing/FrontendTest/Components/Layout/MainLayout.razor similarity index 100% rename from test/FrontendTest/Components/Layout/MainLayout.razor rename to testing/FrontendTest/Components/Layout/MainLayout.razor diff --git a/test/FrontendTest/Components/Layout/MainLayout.razor.css b/testing/FrontendTest/Components/Layout/MainLayout.razor.css similarity index 100% rename from test/FrontendTest/Components/Layout/MainLayout.razor.css rename to testing/FrontendTest/Components/Layout/MainLayout.razor.css diff --git a/test/FrontendTest/Components/Layout/NavMenu.razor b/testing/FrontendTest/Components/Layout/NavMenu.razor similarity index 100% rename from test/FrontendTest/Components/Layout/NavMenu.razor rename to testing/FrontendTest/Components/Layout/NavMenu.razor diff --git a/test/FrontendTest/Components/Layout/NavMenu.razor.css b/testing/FrontendTest/Components/Layout/NavMenu.razor.css similarity index 100% rename from test/FrontendTest/Components/Layout/NavMenu.razor.css rename to testing/FrontendTest/Components/Layout/NavMenu.razor.css diff --git a/test/FrontendTest/Components/Pages/Counter.razor b/testing/FrontendTest/Components/Pages/Counter.razor similarity index 100% rename from test/FrontendTest/Components/Pages/Counter.razor rename to testing/FrontendTest/Components/Pages/Counter.razor diff --git a/test/FrontendTest/Components/Pages/Error.razor b/testing/FrontendTest/Components/Pages/Error.razor similarity index 100% rename from test/FrontendTest/Components/Pages/Error.razor rename to testing/FrontendTest/Components/Pages/Error.razor diff --git a/test/FrontendTest/Components/Pages/Home.razor b/testing/FrontendTest/Components/Pages/Home.razor similarity index 100% rename from test/FrontendTest/Components/Pages/Home.razor rename to testing/FrontendTest/Components/Pages/Home.razor diff --git a/test/FrontendTest/Components/Pages/Weather.razor b/testing/FrontendTest/Components/Pages/Weather.razor similarity index 100% rename from test/FrontendTest/Components/Pages/Weather.razor rename to testing/FrontendTest/Components/Pages/Weather.razor diff --git a/test/FrontendTest/Components/Routes.razor b/testing/FrontendTest/Components/Routes.razor similarity index 100% rename from test/FrontendTest/Components/Routes.razor rename to testing/FrontendTest/Components/Routes.razor diff --git a/test/FrontendTest/Components/_Imports.razor b/testing/FrontendTest/Components/_Imports.razor similarity index 100% rename from test/FrontendTest/Components/_Imports.razor rename to testing/FrontendTest/Components/_Imports.razor diff --git a/test/FrontendTest/DatabaseContext.cs b/testing/FrontendTest/DatabaseContext.cs similarity index 88% rename from test/FrontendTest/DatabaseContext.cs rename to testing/FrontendTest/DatabaseContext.cs index 0dede8a..c1d3c7b 100644 --- a/test/FrontendTest/DatabaseContext.cs +++ b/testing/FrontendTest/DatabaseContext.cs @@ -11,7 +11,7 @@ public class DatabaseContext : HopDbContextBase { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); - optionsBuilder.UseSqlite(@"Data Source=C:\Users\leon\Documents\Projekte\HopFrame\test\RestApiTest\bin\Debug\net8.0\test.db;Mode=ReadWrite;"); + optionsBuilder.UseSqlite(@"Data Source=C:\Users\leon\Documents\Projekte\HopFrame\testing\RestApiTest\bin\Debug\net8.0\test.db;Mode=ReadWrite;"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { diff --git a/test/FrontendTest/FrontendTest.csproj b/testing/FrontendTest/FrontendTest.csproj similarity index 100% rename from test/FrontendTest/FrontendTest.csproj rename to testing/FrontendTest/FrontendTest.csproj diff --git a/test/FrontendTest/Models/Address.cs b/testing/FrontendTest/Models/Address.cs similarity index 100% rename from test/FrontendTest/Models/Address.cs rename to testing/FrontendTest/Models/Address.cs diff --git a/test/FrontendTest/Models/Employee.cs b/testing/FrontendTest/Models/Employee.cs similarity index 100% rename from test/FrontendTest/Models/Employee.cs rename to testing/FrontendTest/Models/Employee.cs diff --git a/test/FrontendTest/Program.cs b/testing/FrontendTest/Program.cs similarity index 100% rename from test/FrontendTest/Program.cs rename to testing/FrontendTest/Program.cs diff --git a/test/FrontendTest/Properties/launchSettings.json b/testing/FrontendTest/Properties/launchSettings.json similarity index 100% rename from test/FrontendTest/Properties/launchSettings.json rename to testing/FrontendTest/Properties/launchSettings.json diff --git a/test/FrontendTest/Providers/AddressProvider.cs b/testing/FrontendTest/Providers/AddressProvider.cs similarity index 100% rename from test/FrontendTest/Providers/AddressProvider.cs rename to testing/FrontendTest/Providers/AddressProvider.cs diff --git a/test/FrontendTest/Providers/EmployeeProvider.cs b/testing/FrontendTest/Providers/EmployeeProvider.cs similarity index 100% rename from test/FrontendTest/Providers/EmployeeProvider.cs rename to testing/FrontendTest/Providers/EmployeeProvider.cs diff --git a/test/FrontendTest/appsettings.json b/testing/FrontendTest/appsettings.json similarity index 100% rename from test/FrontendTest/appsettings.json rename to testing/FrontendTest/appsettings.json diff --git a/test/FrontendTest/wwwroot/app.css b/testing/FrontendTest/wwwroot/app.css similarity index 100% rename from test/FrontendTest/wwwroot/app.css rename to testing/FrontendTest/wwwroot/app.css diff --git a/test/FrontendTest/wwwroot/favicon.png b/testing/FrontendTest/wwwroot/favicon.png similarity index 100% rename from test/FrontendTest/wwwroot/favicon.png rename to testing/FrontendTest/wwwroot/favicon.png diff --git a/test/RestApiTest/.gitignore b/testing/RestApiTest/.gitignore similarity index 100% rename from test/RestApiTest/.gitignore rename to testing/RestApiTest/.gitignore diff --git a/test/RestApiTest/Controllers/TestController.cs b/testing/RestApiTest/Controllers/TestController.cs similarity index 100% rename from test/RestApiTest/Controllers/TestController.cs rename to testing/RestApiTest/Controllers/TestController.cs diff --git a/test/RestApiTest/DatabaseContext.cs b/testing/RestApiTest/DatabaseContext.cs similarity index 88% rename from test/RestApiTest/DatabaseContext.cs rename to testing/RestApiTest/DatabaseContext.cs index ef370c7..42ae5d1 100644 --- a/test/RestApiTest/DatabaseContext.cs +++ b/testing/RestApiTest/DatabaseContext.cs @@ -12,7 +12,7 @@ public class DatabaseContext : HopDbContextBase { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); - optionsBuilder.UseSqlite(@"Data Source=C:\Users\leon\Documents\Projekte\HopFrame\test\RestApiTest\bin\Debug\net8.0\test.db;Mode=ReadWrite;"); + optionsBuilder.UseSqlite(@"Data Source=C:\Users\leon\Documents\Projekte\HopFrame\testing\RestApiTest\bin\Debug\net8.0\test.db;Mode=ReadWrite;"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { diff --git a/test/RestApiTest/Models/Address.cs b/testing/RestApiTest/Models/Address.cs similarity index 100% rename from test/RestApiTest/Models/Address.cs rename to testing/RestApiTest/Models/Address.cs diff --git a/test/RestApiTest/Models/Employee.cs b/testing/RestApiTest/Models/Employee.cs similarity index 100% rename from test/RestApiTest/Models/Employee.cs rename to testing/RestApiTest/Models/Employee.cs diff --git a/test/RestApiTest/Program.cs b/testing/RestApiTest/Program.cs similarity index 100% rename from test/RestApiTest/Program.cs rename to testing/RestApiTest/Program.cs diff --git a/test/RestApiTest/Properties/launchSettings.json b/testing/RestApiTest/Properties/launchSettings.json similarity index 100% rename from test/RestApiTest/Properties/launchSettings.json rename to testing/RestApiTest/Properties/launchSettings.json diff --git a/test/RestApiTest/RestApiTest.csproj b/testing/RestApiTest/RestApiTest.csproj similarity index 100% rename from test/RestApiTest/RestApiTest.csproj rename to testing/RestApiTest/RestApiTest.csproj diff --git a/test/RestApiTest/appsettings.json b/testing/RestApiTest/appsettings.json similarity index 100% rename from test/RestApiTest/appsettings.json rename to testing/RestApiTest/appsettings.json From 85031de3c288cc0f072d6f2be441021bf1b60111 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Sun, 24 Nov 2024 12:40:51 +0100 Subject: [PATCH 02/12] Started creating tests for database module --- .../Data/DatabaseContext.cs | 11 ++ .../HopFrame.Database.Tests.csproj | 29 ++++ .../PermissionValidatorTests.cs | 57 +++++++ .../Repositories/GroupRepositoryTests.cs | 152 ++++++++++++++++++ .../Repositories/PermissionRepositoryTests.cs | 118 ++++++++++++++ .../Repositories/TokenRepositoryTests.cs | 89 ++++++++++ HopFrame.sln | 7 + HopFrame.sln.DotSettings.user | 19 ++- .../HopFrame.Database.csproj | 6 + 9 files changed, 487 insertions(+), 1 deletion(-) create mode 100644 HopFrame.Database.Tests/Data/DatabaseContext.cs create mode 100644 HopFrame.Database.Tests/HopFrame.Database.Tests.csproj create mode 100644 HopFrame.Database.Tests/PermissionValidatorTests.cs create mode 100644 HopFrame.Database.Tests/Repositories/GroupRepositoryTests.cs create mode 100644 HopFrame.Database.Tests/Repositories/PermissionRepositoryTests.cs create mode 100644 HopFrame.Database.Tests/Repositories/TokenRepositoryTests.cs diff --git a/HopFrame.Database.Tests/Data/DatabaseContext.cs b/HopFrame.Database.Tests/Data/DatabaseContext.cs new file mode 100644 index 0000000..7bc93de --- /dev/null +++ b/HopFrame.Database.Tests/Data/DatabaseContext.cs @@ -0,0 +1,11 @@ +using Microsoft.EntityFrameworkCore; + +namespace HopFrame.Database.Tests.Data; + +public class DatabaseContext : HopDbContextBase { + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { + base.OnConfiguring(optionsBuilder); + + optionsBuilder.UseInMemoryDatabase(Guid.NewGuid().ToString()); + } +} \ No newline at end of file diff --git a/HopFrame.Database.Tests/HopFrame.Database.Tests.csproj b/HopFrame.Database.Tests/HopFrame.Database.Tests.csproj new file mode 100644 index 0000000..5246200 --- /dev/null +++ b/HopFrame.Database.Tests/HopFrame.Database.Tests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + diff --git a/HopFrame.Database.Tests/PermissionValidatorTests.cs b/HopFrame.Database.Tests/PermissionValidatorTests.cs new file mode 100644 index 0000000..9d858f8 --- /dev/null +++ b/HopFrame.Database.Tests/PermissionValidatorTests.cs @@ -0,0 +1,57 @@ +namespace HopFrame.Database.Tests; + +public class PermissionValidatorTests { + + [Fact] + public void IncludesPermission_Returns_True_For_ExactPermission() { + // Arrange + var permissions = new [] { "test.permission", "test.permission.exact" }; + var permission = "test.permission.exact"; + + // Act + var hasPermission = PermissionValidator.IncludesPermission(permission, permissions); + + // Assert + Assert.True(hasPermission); + } + + [Fact] + public void IncludesPermission_Returns_True_For_GroupPermission() { + // Arrange + var permissions = new [] { "test.permission.*", "test.permission.exact" }; + var permission = "test.permission.exact.other"; + + // Act + var hasPermission = PermissionValidator.IncludesPermission(permission, permissions); + + // Assert + Assert.True(hasPermission); + } + + [Fact] + public void IncludesPermission_Returns_True_For_StarPermission() { + // Arrange + var permissions = new [] { "test.permission", "test.permission.exact", "*" }; + var permission = "test.permission.exact.other"; + + // Act + var hasPermission = PermissionValidator.IncludesPermission(permission, permissions); + + // Assert + Assert.True(hasPermission); + } + + [Fact] + public void IncludesPermission_Returns_False() { + // Arrange + var permissions = new [] { "test.permission", "test.permission.exact" }; + var permission = "test.permission.exact.other"; + + // Act + var hasPermission = PermissionValidator.IncludesPermission(permission, permissions); + + // Assert + Assert.False(hasPermission); + } + +} \ No newline at end of file diff --git a/HopFrame.Database.Tests/Repositories/GroupRepositoryTests.cs b/HopFrame.Database.Tests/Repositories/GroupRepositoryTests.cs new file mode 100644 index 0000000..84270e0 --- /dev/null +++ b/HopFrame.Database.Tests/Repositories/GroupRepositoryTests.cs @@ -0,0 +1,152 @@ +using HopFrame.Database.Models; +using HopFrame.Database.Repositories; +using HopFrame.Database.Repositories.Implementation; +using HopFrame.Database.Tests.Data; + +namespace HopFrame.Database.Tests.Repositories; + +public class GroupRepositoryTests { + + private async Task<(DatabaseContext, IGroupRepository)> SetupEnvironment(int count = 5) { + var context = new DatabaseContext(); + var repo = new GroupRepository(context); + + for (int i = 0; i < count; i++) { + await context.Groups.AddAsync(new() { + Name = Guid.NewGuid().ToString() + }); + } + await context.SaveChangesAsync(); + + return (context, repo); + } + + [Fact] + public async Task GetPermissionGroups_Returns_AllPermissionGroups() { + // Arrange + var count = 5; + var (context, repo) = await SetupEnvironment(count); + + // Act + var groups = await repo.GetPermissionGroups(); + + // Assert + Assert.Equal(count, groups.Count); + } + + [Fact] + public async Task GetDefaultGroups_Returns_OnlyDefaultGroups() { + // Arrange + var (context, repo) = await SetupEnvironment(); + await context.Groups.AddAsync(new () { + Name = Guid.NewGuid().ToString(), + IsDefaultGroup = true + }); + await context.SaveChangesAsync(); + + // Act + var groups = await repo.GetDefaultGroups(); + + // Assert + Assert.Single(groups); + Assert.True(groups[0].IsDefaultGroup); + } + + [Fact] + public async Task GetUserGroups_Returns_OnlyUserGroups() { + // Arrange + var (context, repo) = await SetupEnvironment(); + await context.Groups.AddAsync(new () { + Name = "group.user_should_have_these", + }); + var user = new User { + Id = Guid.NewGuid(), + Email = "", + Username = "", + Password = "" + }; + user.Permissions = new() { + new() { + User = user, + PermissionName = "group.user_should_have_these" + } + }; + await context.Users.AddAsync(user); + await context.SaveChangesAsync(); + + // Act + var groups = await repo.GetUserGroups(user); + + // Assert + Assert.Single(groups); + Assert.Equal("group.user_should_have_these", groups[0].Name); + } + + [Fact] + public async Task GetPermissionGroup_Returns_OnlyOnePermissionGroup() { + // Arrange + var (context, repo) = await SetupEnvironment(); + await context.Groups.AddAsync(new () { + Name = "group.return" + }); + await context.SaveChangesAsync(); + + // Act + var group = await repo.GetPermissionGroup("group.return"); + + // Assert + Assert.NotNull(group); + Assert.Equal("group.return", group.Name); + } + + [Fact] + public async Task EditPermissionGroup_Should_EditOnePermissionGroup() { + // Arrange + var (context, repo) = await SetupEnvironment(); + var groupToEdit = new PermissionGroup { + Name = "group.edit" + }; + await context.Groups.AddAsync(groupToEdit); + await context.SaveChangesAsync(); + + // Act + groupToEdit.Description = "This description was edited"; + await repo.EditPermissionGroup(groupToEdit); + + // Assert + var group = context.Groups.SingleOrDefault(g => g.Name == "group.edit"); + Assert.NotNull(group); + Assert.Equal("This description was edited", group.Description); + } + + [Fact] + public async Task CreatePermissionGroup_Should_AddOnePermissionGroup() { + // Arrange + var (context, repo) = await SetupEnvironment(0); + var group = new PermissionGroup { + Name = "group", + Description = "Group to add" + }; + + // Act + var result = await repo.CreatePermissionGroup(group); + + // Assert + Assert.Equal(group, result); + Assert.Single(context.Groups); + } + + [Fact] + public async Task DeletePermissionGroup_Should_DeleteOnePermissionGroup() { + // Arrange + var count = 5; + var (context, repo) = await SetupEnvironment(count); + + // Act + await repo.DeletePermissionGroup(context.Groups.First()); + + // Assert + Assert.Equal(count - 1, context.Groups.Count()); + } + +} \ No newline at end of file diff --git a/HopFrame.Database.Tests/Repositories/PermissionRepositoryTests.cs b/HopFrame.Database.Tests/Repositories/PermissionRepositoryTests.cs new file mode 100644 index 0000000..52a9ef7 --- /dev/null +++ b/HopFrame.Database.Tests/Repositories/PermissionRepositoryTests.cs @@ -0,0 +1,118 @@ +using HopFrame.Database.Models; +using HopFrame.Database.Repositories; +using HopFrame.Database.Repositories.Implementation; +using HopFrame.Database.Tests.Data; +using Microsoft.EntityFrameworkCore; + +namespace HopFrame.Database.Tests.Repositories; + +public class PermissionRepositoryTests { + + private async Task<(DatabaseContext, IPermissionRepository)> SetupEnvironment(int count = 5) { + var context = new DatabaseContext(); + var repo = new PermissionRepository(context, new GroupRepository(context)); + + for (int i = 0; i < count; i++) { + await context.Permissions.AddAsync(new () { + PermissionName = Guid.NewGuid().ToString(), + User = CreateTestUser() + }); + } + await context.SaveChangesAsync(); + + return (context, repo); + } + + private User CreateTestUser() => new () { + Username = "", + Email = "", + Password = "" + }; + + [Fact] + public async Task HasPermission_Returns_True() { + // Arrange + var (context, repo) = await SetupEnvironment(); + var user = CreateTestUser(); + await context.Permissions.AddAsync(new() { + PermissionName = "*", + User = user + }); + await context.SaveChangesAsync(); + + // Act + var hasPermission = await repo.HasPermission(user, "*"); + + // Assert + Assert.True(hasPermission); + } + + [Fact] + public async Task AddPermission_Should_AddOnePermission() { + // Arrange + var (context, repo) = await SetupEnvironment(); + var user = CreateTestUser(); + + // Act + var result = await repo.AddPermission(user, "test.permission"); + + // Assert + Assert.NotNull(result); + Assert.Single(context.Permissions + .Include(p => p.User) + .Where(p => p.User.Id == user.Id && p.PermissionName == "test.permission")); + } + + [Fact] + public async Task RemovePermission_Should_RemoveOnePermission() { + // Arrange + var (context, repo) = await SetupEnvironment(); + var user = CreateTestUser(); + await context.Permissions.AddAsync(new() { + PermissionName = "test.permission", + User = user + }); + await context.SaveChangesAsync(); + + // Act + await repo.RemovePermission(user, "test.permission"); + + // Assert + Assert.Empty(context.Permissions + .Include(p => p.User) + .Where(p => p.User.Id == user.Id && p.PermissionName == "test.permission")); + } + + [Fact] + public async Task GetFullPermissions_Return_AllPermissions_Including_Inherited() { + // Arrange + var (context, repo) = await SetupEnvironment(); + var user = CreateTestUser(); + var group = new PermissionGroup { + Name = "group.admin" + }; + await context.Permissions.AddRangeAsync(new List { + new() { + PermissionName = "test.permission.inherited", + Group = group + }, + new() { + PermissionName = "test.permission", + User = user + }, + new() { + PermissionName = "group.admin", + User = user + } + }); + await context.SaveChangesAsync(); + + // Act + var perms = await repo.GetFullPermissions(user); + + // Assert + Assert.NotNull(perms); + Assert.Equal(3, perms.Count); + } + +} \ No newline at end of file diff --git a/HopFrame.Database.Tests/Repositories/TokenRepositoryTests.cs b/HopFrame.Database.Tests/Repositories/TokenRepositoryTests.cs new file mode 100644 index 0000000..97e382a --- /dev/null +++ b/HopFrame.Database.Tests/Repositories/TokenRepositoryTests.cs @@ -0,0 +1,89 @@ +using HopFrame.Database.Models; +using HopFrame.Database.Repositories; +using HopFrame.Database.Repositories.Implementation; +using HopFrame.Database.Tests.Data; +using Microsoft.EntityFrameworkCore; + +namespace HopFrame.Database.Tests.Repositories; + +public class TokenRepositoryTests { + + private async Task<(DatabaseContext, ITokenRepository)> SetupEnvironment(int count = 5) { + var context = new DatabaseContext(); + var repo = new TokenRepository(context); + + for (int i = 0; i < count; i++) { + await context.Tokens.AddAsync(new() { + Content = Guid.NewGuid(), + Owner = CreateTestUser(), + Type = Token.AccessTokenType + }); + } + await context.SaveChangesAsync(); + + return (context, repo); + } + + private User CreateTestUser() => new () { + Username = "", + Email = "", + Password = "" + }; + + [Fact] + public async Task GetToken_Return_CorrectToken() { + // Arrange + var (context, repo) = await SetupEnvironment(); + var token = context.Tokens.First(); + + // Act + var result = await repo.GetToken(token.Content.ToString()); + + // Assert + Assert.Equal(token, result); + } + + [Fact] + public async Task CreateToken_Should_CreateOneToken() { + // Arrange + var (context, repo) = await SetupEnvironment(0); + + // Act + var result = await repo.CreateToken(Token.AccessTokenType, CreateTestUser()); + + // Assert + Assert.NotEmpty(context.Tokens); + Assert.Equal(result, context.Tokens.First()); + } + + [Fact] + public async Task DeleteUserTokens_Should_DeleteOnlyUserTokens() { + // Arrange + var dummyCount = 5; + var (context, repo) = await SetupEnvironment(dummyCount); + var user = CreateTestUser(); + await context.Tokens.AddRangeAsync(new List { + new() { + Content = Guid.NewGuid(), + Owner = user, + Type = Token.AccessTokenType + }, + new() { + Content = Guid.NewGuid(), + Owner = user, + Type = Token.RefreshTokenType + } + }); + await context.SaveChangesAsync(); + + // Act + await repo.DeleteUserTokens(user); + + // Assert + Assert.Equal(dummyCount, context.Tokens.Count()); + Assert.Empty(context.Tokens + .Include(t => t.Owner) + .Where(t => t.Owner == user)); + } + +} \ No newline at end of file diff --git a/HopFrame.sln b/HopFrame.sln index 269b7be..9177f94 100644 --- a/HopFrame.sln +++ b/HopFrame.sln @@ -20,6 +20,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Testing", "Testing", "{EEA2 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{1D98E5DE-CB8B-4C1C-A319-D49AC137441A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Database.Tests", "HopFrame.Database.Tests\HopFrame.Database.Tests.csproj", "{1CAAC943-B8FE-48DD-9712-92699647DE18}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -54,6 +56,10 @@ Global {02D9F10A-664A-4EF7-BF19-310C26FF4DEB}.Debug|Any CPU.Build.0 = Debug|Any CPU {02D9F10A-664A-4EF7-BF19-310C26FF4DEB}.Release|Any CPU.ActiveCfg = Release|Any CPU {02D9F10A-664A-4EF7-BF19-310C26FF4DEB}.Release|Any CPU.Build.0 = Release|Any CPU + {1CAAC943-B8FE-48DD-9712-92699647DE18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1CAAC943-B8FE-48DD-9712-92699647DE18}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1CAAC943-B8FE-48DD-9712-92699647DE18}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1CAAC943-B8FE-48DD-9712-92699647DE18}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {1E821490-AEDC-4F55-B758-52F4FADAB53A} = {64EDCBED-A84F-4936-8697-78DC43CB2427} @@ -63,5 +69,6 @@ Global {02D9F10A-664A-4EF7-BF19-310C26FF4DEB} = {64EDCBED-A84F-4936-8697-78DC43CB2427} {8F983A37-63CF-48D5-988D-58B78EF8AECD} = {EEA20D27-D471-44AF-A287-C0E068D93182} {921159CE-AF75-44C3-A3F9-6B9B1A4E85CF} = {EEA20D27-D471-44AF-A287-C0E068D93182} + {1CAAC943-B8FE-48DD-9712-92699647DE18} = {1D98E5DE-CB8B-4C1C-A319-D49AC137441A} EndGlobalSection EndGlobal diff --git a/HopFrame.sln.DotSettings.user b/HopFrame.sln.DotSettings.user index a38eed3..1798b6e 100644 --- a/HopFrame.sln.DotSettings.user +++ b/HopFrame.sln.DotSettings.user @@ -5,4 +5,21 @@ <AssemblyExplorer> <Assembly Path="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\7.0.16\ref\net7.0\System.ComponentModel.Annotations.dll" /> <Assembly Path="C:\Users\Remote\.nuget\packages\blazorstrap\5.2.100.61524\lib\net7.0\BlazorStrap.dll" /> -</AssemblyExplorer> \ No newline at end of file +</AssemblyExplorer> + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/HopFrame.Database/HopFrame.Database.csproj b/src/HopFrame.Database/HopFrame.Database.csproj index de55cd5..621c7b9 100644 --- a/src/HopFrame.Database/HopFrame.Database.csproj +++ b/src/HopFrame.Database/HopFrame.Database.csproj @@ -22,4 +22,10 @@ + + + <_Parameter1>HopFrame.Database.Tests + + + From b7eca1937cb5209cc26db5fa81e87adeb57f5453 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Sun, 24 Nov 2024 12:43:41 +0100 Subject: [PATCH 03/12] Attempted to fix test workflow not being restored --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6287fe9..d8ae244 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,7 +21,7 @@ build: test: stage: test script: - - dotnet test --no-restore --verbosity normal + - dotnet test --verbosity normal publish: stage: publish From da45a84f61451195c1a80ce9a84dc30cd191b6e5 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Sun, 24 Nov 2024 12:47:23 +0100 Subject: [PATCH 04/12] Moved test project to correct folder --- HopFrame.sln | 2 +- .../HopFrame.Database.Tests}/Data/DatabaseContext.cs | 0 .../HopFrame.Database.Tests}/HopFrame.Database.Tests.csproj | 1 + .../HopFrame.Database.Tests}/PermissionValidatorTests.cs | 0 .../Repositories/GroupRepositoryTests.cs | 0 .../Repositories/PermissionRepositoryTests.cs | 0 .../Repositories/TokenRepositoryTests.cs | 0 7 files changed, 2 insertions(+), 1 deletion(-) rename {HopFrame.Database.Tests => tests/HopFrame.Database.Tests}/Data/DatabaseContext.cs (100%) rename {HopFrame.Database.Tests => tests/HopFrame.Database.Tests}/HopFrame.Database.Tests.csproj (91%) rename {HopFrame.Database.Tests => tests/HopFrame.Database.Tests}/PermissionValidatorTests.cs (100%) rename {HopFrame.Database.Tests => tests/HopFrame.Database.Tests}/Repositories/GroupRepositoryTests.cs (100%) rename {HopFrame.Database.Tests => tests/HopFrame.Database.Tests}/Repositories/PermissionRepositoryTests.cs (100%) rename {HopFrame.Database.Tests => tests/HopFrame.Database.Tests}/Repositories/TokenRepositoryTests.cs (100%) diff --git a/HopFrame.sln b/HopFrame.sln index 9177f94..7a60cf4 100644 --- a/HopFrame.sln +++ b/HopFrame.sln @@ -20,7 +20,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Testing", "Testing", "{EEA2 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{1D98E5DE-CB8B-4C1C-A319-D49AC137441A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Database.Tests", "HopFrame.Database.Tests\HopFrame.Database.Tests.csproj", "{1CAAC943-B8FE-48DD-9712-92699647DE18}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Database.Tests", "tests\HopFrame.Database.Tests\HopFrame.Database.Tests.csproj", "{1CAAC943-B8FE-48DD-9712-92699647DE18}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/HopFrame.Database.Tests/Data/DatabaseContext.cs b/tests/HopFrame.Database.Tests/Data/DatabaseContext.cs similarity index 100% rename from HopFrame.Database.Tests/Data/DatabaseContext.cs rename to tests/HopFrame.Database.Tests/Data/DatabaseContext.cs diff --git a/HopFrame.Database.Tests/HopFrame.Database.Tests.csproj b/tests/HopFrame.Database.Tests/HopFrame.Database.Tests.csproj similarity index 91% rename from HopFrame.Database.Tests/HopFrame.Database.Tests.csproj rename to tests/HopFrame.Database.Tests/HopFrame.Database.Tests.csproj index 5246200..05a23e3 100644 --- a/HopFrame.Database.Tests/HopFrame.Database.Tests.csproj +++ b/tests/HopFrame.Database.Tests/HopFrame.Database.Tests.csproj @@ -23,6 +23,7 @@ + diff --git a/HopFrame.Database.Tests/PermissionValidatorTests.cs b/tests/HopFrame.Database.Tests/PermissionValidatorTests.cs similarity index 100% rename from HopFrame.Database.Tests/PermissionValidatorTests.cs rename to tests/HopFrame.Database.Tests/PermissionValidatorTests.cs diff --git a/HopFrame.Database.Tests/Repositories/GroupRepositoryTests.cs b/tests/HopFrame.Database.Tests/Repositories/GroupRepositoryTests.cs similarity index 100% rename from HopFrame.Database.Tests/Repositories/GroupRepositoryTests.cs rename to tests/HopFrame.Database.Tests/Repositories/GroupRepositoryTests.cs diff --git a/HopFrame.Database.Tests/Repositories/PermissionRepositoryTests.cs b/tests/HopFrame.Database.Tests/Repositories/PermissionRepositoryTests.cs similarity index 100% rename from HopFrame.Database.Tests/Repositories/PermissionRepositoryTests.cs rename to tests/HopFrame.Database.Tests/Repositories/PermissionRepositoryTests.cs diff --git a/HopFrame.Database.Tests/Repositories/TokenRepositoryTests.cs b/tests/HopFrame.Database.Tests/Repositories/TokenRepositoryTests.cs similarity index 100% rename from HopFrame.Database.Tests/Repositories/TokenRepositoryTests.cs rename to tests/HopFrame.Database.Tests/Repositories/TokenRepositoryTests.cs From fca6ef4fa6c96f3265c57a91d41ab73bf068ad50 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Sun, 24 Nov 2024 16:01:33 +0100 Subject: [PATCH 05/12] Implemented all tests for database module --- HopFrame.sln.DotSettings.user | 10 + .../HopFrame.Database.Tests.csproj | 1 - .../Repositories/UserRepositoryTests.cs | 184 ++++++++++++++++++ 3 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 tests/HopFrame.Database.Tests/Repositories/UserRepositoryTests.cs diff --git a/HopFrame.sln.DotSettings.user b/HopFrame.sln.DotSettings.user index 1798b6e..a0c3151 100644 --- a/HopFrame.sln.DotSettings.user +++ b/HopFrame.sln.DotSettings.user @@ -1,11 +1,21 @@  ForceIncluded + ForceIncluded ForceIncluded + ForceIncluded ForceIncluded <AssemblyExplorer> <Assembly Path="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\7.0.16\ref\net7.0\System.ComponentModel.Annotations.dll" /> <Assembly Path="C:\Users\Remote\.nuget\packages\blazorstrap\5.2.100.61524\lib\net7.0\BlazorStrap.dll" /> </AssemblyExplorer> + <SessionState ContinuousTestingMode="0" IsActive="True" Name="UserRepositoryTests" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <TestAncestor> + <TestId>xUnit::1CAAC943-B8FE-48DD-9712-92699647DE18::net8.0::HopFrame.Database.Tests.Repositories.UserRepositoryTests</TestId> + </TestAncestor> +</SessionState> + + + diff --git a/tests/HopFrame.Database.Tests/HopFrame.Database.Tests.csproj b/tests/HopFrame.Database.Tests/HopFrame.Database.Tests.csproj index 05a23e3..4435c88 100644 --- a/tests/HopFrame.Database.Tests/HopFrame.Database.Tests.csproj +++ b/tests/HopFrame.Database.Tests/HopFrame.Database.Tests.csproj @@ -24,7 +24,6 @@ - diff --git a/tests/HopFrame.Database.Tests/Repositories/UserRepositoryTests.cs b/tests/HopFrame.Database.Tests/Repositories/UserRepositoryTests.cs new file mode 100644 index 0000000..7d8362b --- /dev/null +++ b/tests/HopFrame.Database.Tests/Repositories/UserRepositoryTests.cs @@ -0,0 +1,184 @@ +using HopFrame.Database.Models; +using HopFrame.Database.Repositories; +using HopFrame.Database.Repositories.Implementation; +using HopFrame.Database.Tests.Data; + +namespace HopFrame.Database.Tests.Repositories; + +public class UserRepositoryTests { + + private async Task<(DatabaseContext, IUserRepository)> SetupEnvironment(int count = 5) { + var context = new DatabaseContext(); + var repo = new UserRepository(context, new GroupRepository(context)); + + for (int i = 0; i < count; i++) { + await context.Users.AddAsync(CreateTestUser()); + } + await context.SaveChangesAsync(); + + return (context, repo); + } + + private User CreateTestUser() => new () { + Username = "", + Email = "", + Password = "" + }; + + [Fact] + public async Task GetUsers_Returns_AllUsers() { + // Arrange + var count = 5; + var (context, repo) = await SetupEnvironment(count); + + // Act + var users = await repo.GetUsers(); + + // Assert + Assert.NotNull(users); + Assert.Equal(count, users.Count); + } + + [Fact] + public async Task GetUser_Returns_SingleUser() { + // Arrange + var (context, repo) = await SetupEnvironment(); + var guid = context.Users.First().Id; + + // Act + var user = await repo.GetUser(guid); + + // Assert + Assert.NotNull(user); + Assert.Equal(guid, user.Id); + } + + [Fact] + public async Task GetUserByMail_Returns_SingleUser() { + // Arrange + var (context, repo) = await SetupEnvironment(); + var user = CreateTestUser(); + user.Email = "test@example.com"; + await context.Users.AddAsync(user); + await context.SaveChangesAsync(); + + // Act + var result = await repo.GetUserByEmail("test@example.com"); + + // Assert + Assert.NotNull(result); + Assert.Equal(user, result); + } + + [Fact] + public async Task GetUserByUsername_Returns_SingleUser() { + // Arrange + var (context, repo) = await SetupEnvironment(); + var user = CreateTestUser(); + user.Username = "test.user"; + await context.Users.AddAsync(user); + await context.SaveChangesAsync(); + + // Act + var result = await repo.GetUserByUsername("test.user"); + + // Assert + Assert.NotNull(result); + Assert.Equal(user, result); + } + + [Fact] + public async Task AddUser_Returns_NewUser() { + // Arrange + var count = 5; + var (context, repo) = await SetupEnvironment(count); + + // Act + var user = await repo.AddUser(new User { + Username = "test.user", + Email = "test@example.com", + Password = "changeme" + }); + + // Assert + Assert.NotNull(user); + Assert.Equal(count + 1, context.Users.Count()); + } + + [Fact] + public async Task UpdateUser_Should_UpdateAUser() { + // Arrange + var (context, repo) = await SetupEnvironment(); + var user = context.Users.First(); + + // Act + user.Username = "test.user"; + await repo.UpdateUser(user); + + // Assert + var result = context.Users.SingleOrDefault(u => u.Username == "test.user"); + Assert.NotNull(result); + } + + [Fact] + public async Task DeleteUser_Should_DeleteSingleUser() { + // Arrange + var count = 5; + var (context, repo) = await SetupEnvironment(count); + var user = context.Users.First(); + + // Act + await repo.DeleteUser(user); + + // Assert + Assert.Equal(count - 1, context.Users.Count()); + Assert.DoesNotContain(user, context.Users); + } + + [Fact] + public async Task CheckUserPassword_Returns_True() { + // Arrange + var (context, repo) = await SetupEnvironment(0); + var user = CreateTestUser(); + user.Password = "changeme"; + user = await repo.AddUser(user); + + // Act + var result = await repo.CheckUserPassword(user, "changeme"); + + // Assert + Assert.True(result); + } + + [Fact] + public async Task CheckUserPassword_Returns_False() { + // Arrange + var (context, repo) = await SetupEnvironment(0); + var user = CreateTestUser(); + user.Password = "changeme"; + user = await repo.AddUser(user); + + // Act + var result = await repo.CheckUserPassword(user, "dontchangeme"); + + // Assert + Assert.False(result); + } + + [Fact] + public async Task ChangePassword_Should_ChangeUserPassword() { + // Arrange + var (context, repo) = await SetupEnvironment(0); + var user = CreateTestUser(); + user.Password = "changeme"; + user = await repo.AddUser(user); + + // Act + await repo.ChangePassword(user, "changedme"); + + // Assert + var result = await repo.CheckUserPassword(user, "changedme"); + Assert.True(result); + } + +} \ No newline at end of file From 14c82f4f0638a39b5b8a4f086dadd6df22ffbcce Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Sun, 24 Nov 2024 17:18:35 +0100 Subject: [PATCH 06/12] Implemented security module tests --- HopFrame.sln | 7 + HopFrame.sln.DotSettings.user | 18 ++- .../Authentication/HopFrameAuthentication.cs | 1 - .../HopFrame.Database.Tests.csproj | 1 - .../AuthenticationTests.cs | 141 ++++++++++++++++++ .../AuthorizationTests.cs | 92 ++++++++++++ .../HopFrame.Security.Tests.csproj | 34 +++++ 7 files changed, 290 insertions(+), 4 deletions(-) create mode 100644 tests/HopFrame.Security.Tests/AuthenticationTests.cs create mode 100644 tests/HopFrame.Security.Tests/AuthorizationTests.cs create mode 100644 tests/HopFrame.Security.Tests/HopFrame.Security.Tests.csproj diff --git a/HopFrame.sln b/HopFrame.sln index 7a60cf4..94da369 100644 --- a/HopFrame.sln +++ b/HopFrame.sln @@ -22,6 +22,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{1D98E5DE EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Database.Tests", "tests\HopFrame.Database.Tests\HopFrame.Database.Tests.csproj", "{1CAAC943-B8FE-48DD-9712-92699647DE18}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Security.Tests", "tests\HopFrame.Security.Tests\HopFrame.Security.Tests.csproj", "{6747753A-6059-48F1-B779-D73765A373A6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -60,6 +62,10 @@ Global {1CAAC943-B8FE-48DD-9712-92699647DE18}.Debug|Any CPU.Build.0 = Debug|Any CPU {1CAAC943-B8FE-48DD-9712-92699647DE18}.Release|Any CPU.ActiveCfg = Release|Any CPU {1CAAC943-B8FE-48DD-9712-92699647DE18}.Release|Any CPU.Build.0 = Release|Any CPU + {6747753A-6059-48F1-B779-D73765A373A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6747753A-6059-48F1-B779-D73765A373A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6747753A-6059-48F1-B779-D73765A373A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6747753A-6059-48F1-B779-D73765A373A6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {1E821490-AEDC-4F55-B758-52F4FADAB53A} = {64EDCBED-A84F-4936-8697-78DC43CB2427} @@ -70,5 +76,6 @@ Global {8F983A37-63CF-48D5-988D-58B78EF8AECD} = {EEA20D27-D471-44AF-A287-C0E068D93182} {921159CE-AF75-44C3-A3F9-6B9B1A4E85CF} = {EEA20D27-D471-44AF-A287-C0E068D93182} {1CAAC943-B8FE-48DD-9712-92699647DE18} = {1D98E5DE-CB8B-4C1C-A319-D49AC137441A} + {6747753A-6059-48F1-B779-D73765A373A6} = {1D98E5DE-CB8B-4C1C-A319-D49AC137441A} EndGlobalSection EndGlobal diff --git a/HopFrame.sln.DotSettings.user b/HopFrame.sln.DotSettings.user index a0c3151..4f1a50c 100644 --- a/HopFrame.sln.DotSettings.user +++ b/HopFrame.sln.DotSettings.user @@ -1,4 +1,5 @@  + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -8,9 +9,16 @@ <Assembly Path="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\7.0.16\ref\net7.0\System.ComponentModel.Annotations.dll" /> <Assembly Path="C:\Users\Remote\.nuget\packages\blazorstrap\5.2.100.61524\lib\net7.0\BlazorStrap.dll" /> </AssemblyExplorer> - <SessionState ContinuousTestingMode="0" IsActive="True" Name="UserRepositoryTests" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + + + + + + + + <SessionState ContinuousTestingMode="0" Name="Authentication_With_NoToken_Should_Fail" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> <TestAncestor> - <TestId>xUnit::1CAAC943-B8FE-48DD-9712-92699647DE18::net8.0::HopFrame.Database.Tests.Repositories.UserRepositoryTests</TestId> + <TestId>xUnit::6747753A-6059-48F1-B779-D73765A373A6::net8.0::HopFrame.Security.Tests.AuthenticationTests.Authentication_With_NoToken_Should_Fail</TestId> </TestAncestor> </SessionState> @@ -27,6 +35,12 @@ + + + + + + diff --git a/src/HopFrame.Security/Authentication/HopFrameAuthentication.cs b/src/HopFrame.Security/Authentication/HopFrameAuthentication.cs index 9c65b14..8709f91 100644 --- a/src/HopFrame.Security/Authentication/HopFrameAuthentication.cs +++ b/src/HopFrame.Security/Authentication/HopFrameAuthentication.cs @@ -17,7 +17,6 @@ public class HopFrameAuthentication( UrlEncoder encoder, ISystemClock clock, ITokenRepository tokens, - IUserRepository users, IPermissionRepository perms) : AuthenticationHandler(options, logger, encoder, clock) { diff --git a/tests/HopFrame.Database.Tests/HopFrame.Database.Tests.csproj b/tests/HopFrame.Database.Tests/HopFrame.Database.Tests.csproj index 4435c88..ce93d67 100644 --- a/tests/HopFrame.Database.Tests/HopFrame.Database.Tests.csproj +++ b/tests/HopFrame.Database.Tests/HopFrame.Database.Tests.csproj @@ -11,7 +11,6 @@ - diff --git a/tests/HopFrame.Security.Tests/AuthenticationTests.cs b/tests/HopFrame.Security.Tests/AuthenticationTests.cs new file mode 100644 index 0000000..2594cbb --- /dev/null +++ b/tests/HopFrame.Security.Tests/AuthenticationTests.cs @@ -0,0 +1,141 @@ +using System.Text.Encodings.Web; +using HopFrame.Database.Models; +using HopFrame.Database.Repositories; +using HopFrame.Security.Authentication; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; + +namespace HopFrame.Security.Tests; + +public class AuthenticationTests { + + private async Task SetupEnvironment(Token correctToken = null, string providedToken = null) { + var options = new Mock>(); + options + .Setup(x => x.Get(It.IsAny())) + .Returns(new AuthenticationSchemeOptions()); + + var logger = new Mock(); + logger + .Setup(x => x.CreateLogger(It.IsAny())) + .Returns(new Mock>().Object); + + var encoder = new Mock(); + var clock = new Mock(); + var tokens = new Mock(); + var perms = new Mock(); + + var provideCorrectToken = correctToken is null; + correctToken ??= new Token { + Content = Guid.NewGuid(), + CreatedAt = DateTime.Now, + Type = Token.AccessTokenType, + Owner = new User { + Id = Guid.NewGuid() + } + }; + + tokens + .Setup(x => x.GetToken(It.Is(t => t == correctToken.Content.ToString()))) + .ReturnsAsync(correctToken); + + perms + .Setup(x => x.GetFullPermissions(It.IsAny())) + .ReturnsAsync(new List()); + + var auth = new HopFrameAuthentication(options.Object, logger.Object, encoder.Object, clock.Object, tokens.Object, perms.Object); + var context = new DefaultHttpContext(); + if (provideCorrectToken) + context.HttpContext.Request.Headers.Append(HopFrameAuthentication.SchemeName, correctToken.Content.ToString()); + if (providedToken is not null) + context.HttpContext.Request.Headers.Append(HopFrameAuthentication.SchemeName, providedToken); + + await auth.InitializeAsync(new AuthenticationScheme(HopFrameAuthentication.SchemeName, null, typeof(HopFrameAuthentication)), context); + return auth; + } + + [Fact] + public async Task Authentication_Should_Succeed() { + // Arrange + var auth = await SetupEnvironment(); + + // Act + var result = await auth.AuthenticateAsync(); + + // Assert + Assert.True(result.Succeeded); + } + + [Fact] + public async Task Authentication_With_NoToken_Should_Fail() { + // Arrange + var auth = await SetupEnvironment(new Token()); + + // Act + var result = await auth.AuthenticateAsync(); + + // Assert + Assert.False(result.Succeeded); + Assert.NotNull(result.Failure); + Assert.Equal("No Access Token provided", result.Failure.Message); + } + + [Fact] + public async Task Authentication_With_InvalidToken_Should_Fail() { + // Arrange + var auth = await SetupEnvironment(null, Guid.NewGuid().ToString()); + + // Act + var result = await auth.AuthenticateAsync(); + + // Assert + Assert.False(result.Succeeded); + Assert.NotNull(result.Failure); + Assert.Equal("The provided Access Token does not exist", result.Failure.Message); + } + + [Fact] + public async Task Authentication_With_ExpiredToken_Should_Fail() { + // Arrange + var token = new Token { + Content = Guid.NewGuid(), + CreatedAt = DateTime.MinValue, + Type = Token.AccessTokenType, + Owner = new User() + }; + var auth = await SetupEnvironment(token, token.Content.ToString()); + + // Act + var result = await auth.AuthenticateAsync(); + + // Assert + Assert.False(result.Succeeded); + Assert.NotNull(result.Failure); + Assert.Equal("The provided Access Token is expired", result.Failure.Message); + } + + [Fact] + public async Task Authentication_With_UnownedToken_Should_Fail() { + // Arrange + var token = new Token { + Content = Guid.NewGuid(), + CreatedAt = DateTime.Now, + Type = Token.AccessTokenType, + Owner = null + }; + var auth = await SetupEnvironment(token, token.Content.ToString()); + + // Act + var result = await auth.AuthenticateAsync(); + + // Assert + Assert.False(result.Succeeded); + Assert.NotNull(result.Failure); + Assert.Equal("The provided Access Token does not match any user", result.Failure.Message); + } + + +} \ No newline at end of file diff --git a/tests/HopFrame.Security.Tests/AuthorizationTests.cs b/tests/HopFrame.Security.Tests/AuthorizationTests.cs new file mode 100644 index 0000000..730dfde --- /dev/null +++ b/tests/HopFrame.Security.Tests/AuthorizationTests.cs @@ -0,0 +1,92 @@ +using System.Security.Claims; +using HopFrame.Security.Authentication; +using HopFrame.Security.Authorization; +using HopFrame.Security.Claims; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using Moq; + +namespace HopFrame.Security.Tests; + +public class AuthorizationTests { + + private (AuthorizedFilter, AuthorizationFilterContext) SetupEnvironment(string[] userPermissions, string[] requiredPermissions, bool accessTokenProvided = true) { + var filter = new AuthorizedFilter(requiredPermissions); + + var httpContext = new DefaultHttpContext(); + var actionContext = new ActionContext { HttpContext = httpContext, RouteData = new RouteData(), ActionDescriptor = new ActionDescriptor() }; + var context = new Mock(MockBehavior.Default, actionContext, new List()); + + context + .Setup(x => x.Filters) + .Returns(new List()); + + context.SetupProperty(c => c.Result); + + var claims = new List { + new(HopFrameClaimTypes.UserId, Guid.NewGuid().ToString()) + }; + if (accessTokenProvided) + claims.Add(new (HopFrameClaimTypes.AccessTokenId, Guid.NewGuid().ToString())); + claims.AddRange(userPermissions.Select(perm => new Claim(HopFrameClaimTypes.Permission, perm))); + + context.Object.HttpContext.User.AddIdentity(new ClaimsIdentity(claims, HopFrameAuthentication.SchemeName)); + + return (filter, context.Object); + } + + [Fact] + public void OnAuthorization_Should_Succeed() { + // Arrange + var (filter, context) = SetupEnvironment(["test.permission"], ["test.permission"]); + + // Act + filter.OnAuthorization(context); + + // Assert + Assert.Null(context.Result); + } + + [Fact] + public void OnAuthorization_With_NoToken_Should_Fail() { + // Arrange + var (filter, context) = SetupEnvironment([], [], false); + + // Act + filter.OnAuthorization(context); + + // Assert + Assert.NotNull(context.Result); + Assert.IsType(context.Result); + } + + [Fact] + public void OnAuthorization_With_NoPermissions_Should_Fail() { + // Arrange + var (filter, context) = SetupEnvironment([], ["test.permission"]); + + // Act + filter.OnAuthorization(context); + + // Assert + Assert.NotNull(context.Result); + Assert.IsType(context.Result); + } + + [Fact] + public void OnAuthorization_With_InsufficientPermissions_Should_Fail() { + // Arrange + var (filter, context) = SetupEnvironment(["permission.other"], ["test.permission"]); + + // Act + filter.OnAuthorization(context); + + // Assert + Assert.NotNull(context.Result); + Assert.IsType(context.Result); + } + +} \ No newline at end of file diff --git a/tests/HopFrame.Security.Tests/HopFrame.Security.Tests.csproj b/tests/HopFrame.Security.Tests/HopFrame.Security.Tests.csproj new file mode 100644 index 0000000..c1feef2 --- /dev/null +++ b/tests/HopFrame.Security.Tests/HopFrame.Security.Tests.csproj @@ -0,0 +1,34 @@ + + + + net8.0 + enable + disable + + false + true + + + + + + + + + + + + + + + + + ..\..\..\..\..\..\..\Program Files\dotnet\shared\Microsoft.AspNetCore.App\8.0.11\Microsoft.AspNetCore.Authentication.dll + + + + + + + + From a4d1d3227be2ceae7d48c37e91f55680582c85d8 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Mon, 9 Dec 2024 18:41:51 +0100 Subject: [PATCH 07/12] Created tests for HopFrame.Api --- HopFrame.sln | 7 + HopFrame.sln.DotSettings.user | 18 +- src/HopFrame.Api/HopFrame.Api.csproj | 6 + .../Logic/Implementation/AuthLogic.cs | 14 +- tests/HopFrame.Api.Tests/AuthLogicTests.cs | 403 ++++++++++++++++++ .../Extensions/HttpContextExtensions.cs | 29 ++ .../HopFrame.Api.Tests.csproj | 28 ++ 7 files changed, 493 insertions(+), 12 deletions(-) create mode 100644 tests/HopFrame.Api.Tests/AuthLogicTests.cs create mode 100644 tests/HopFrame.Api.Tests/Extensions/HttpContextExtensions.cs create mode 100644 tests/HopFrame.Api.Tests/HopFrame.Api.Tests.csproj diff --git a/HopFrame.sln b/HopFrame.sln index 94da369..b5779b1 100644 --- a/HopFrame.sln +++ b/HopFrame.sln @@ -24,6 +24,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Database.Tests", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Security.Tests", "tests\HopFrame.Security.Tests\HopFrame.Security.Tests.csproj", "{6747753A-6059-48F1-B779-D73765A373A6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Api.Tests", "tests\HopFrame.Api.Tests\HopFrame.Api.Tests.csproj", "{25DE1510-47E5-46FF-89A4-B9F99542218E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -66,6 +68,10 @@ Global {6747753A-6059-48F1-B779-D73765A373A6}.Debug|Any CPU.Build.0 = Debug|Any CPU {6747753A-6059-48F1-B779-D73765A373A6}.Release|Any CPU.ActiveCfg = Release|Any CPU {6747753A-6059-48F1-B779-D73765A373A6}.Release|Any CPU.Build.0 = Release|Any CPU + {25DE1510-47E5-46FF-89A4-B9F99542218E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {25DE1510-47E5-46FF-89A4-B9F99542218E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {25DE1510-47E5-46FF-89A4-B9F99542218E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {25DE1510-47E5-46FF-89A4-B9F99542218E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {1E821490-AEDC-4F55-B758-52F4FADAB53A} = {64EDCBED-A84F-4936-8697-78DC43CB2427} @@ -77,5 +83,6 @@ Global {921159CE-AF75-44C3-A3F9-6B9B1A4E85CF} = {EEA20D27-D471-44AF-A287-C0E068D93182} {1CAAC943-B8FE-48DD-9712-92699647DE18} = {1D98E5DE-CB8B-4C1C-A319-D49AC137441A} {6747753A-6059-48F1-B779-D73765A373A6} = {1D98E5DE-CB8B-4C1C-A319-D49AC137441A} + {25DE1510-47E5-46FF-89A4-B9F99542218E} = {1D98E5DE-CB8B-4C1C-A319-D49AC137441A} EndGlobalSection EndGlobal diff --git a/HopFrame.sln.DotSettings.user b/HopFrame.sln.DotSettings.user index 4f1a50c..ea6ada4 100644 --- a/HopFrame.sln.DotSettings.user +++ b/HopFrame.sln.DotSettings.user @@ -2,6 +2,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -16,11 +17,18 @@ - <SessionState ContinuousTestingMode="0" Name="Authentication_With_NoToken_Should_Fail" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> - <TestAncestor> - <TestId>xUnit::6747753A-6059-48F1-B779-D73765A373A6::net8.0::HopFrame.Security.Tests.AuthenticationTests.Authentication_With_NoToken_Should_Fail</TestId> - </TestAncestor> -</SessionState> + + + + + + + + + + + + diff --git a/src/HopFrame.Api/HopFrame.Api.csproj b/src/HopFrame.Api/HopFrame.Api.csproj index 744a466..c8b21c4 100644 --- a/src/HopFrame.Api/HopFrame.Api.csproj +++ b/src/HopFrame.Api/HopFrame.Api.csproj @@ -22,4 +22,10 @@ + + + <_Parameter1>HopFrame.Api.Tests + + + diff --git a/src/HopFrame.Api/Logic/Implementation/AuthLogic.cs b/src/HopFrame.Api/Logic/Implementation/AuthLogic.cs index e792add..c1f3c90 100644 --- a/src/HopFrame.Api/Logic/Implementation/AuthLogic.cs +++ b/src/HopFrame.Api/Logic/Implementation/AuthLogic.cs @@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Http; namespace HopFrame.Api.Logic.Implementation; -public class AuthLogic(IUserRepository users, ITokenRepository tokens, ITokenContext tokenContext, IHttpContextAccessor accessor) : IAuthLogic { +internal class AuthLogic(IUserRepository users, ITokenRepository tokens, ITokenContext tokenContext, IHttpContextAccessor accessor) : IAuthLogic { public async Task>> Login(UserLogin login) { var user = await users.GetUserByEmail(login.Email); @@ -38,7 +38,7 @@ public class AuthLogic(IUserRepository users, ITokenRepository tokens, ITokenCon public async Task>> Register(UserRegister register) { if (register.Password.Length < 8) - return LogicResult>.Conflict("Password needs to be at least 8 characters long"); + return LogicResult>.BadRequest("Password needs to be at least 8 characters long"); var allUsers = await users.GetUsers(); if (allUsers.Any(user => user.Username == register.Username || user.Email == register.Email)) @@ -71,18 +71,18 @@ public class AuthLogic(IUserRepository users, ITokenRepository tokens, ITokenCon var refreshToken = accessor.HttpContext?.Request.Cookies[ITokenContext.RefreshTokenType]; if (string.IsNullOrEmpty(refreshToken)) - return LogicResult>.Conflict("Refresh token not provided"); + return LogicResult>.BadRequest("Refresh token not provided"); var token = await tokens.GetToken(refreshToken); - if (token.Type != Token.RefreshTokenType) - return LogicResult>.BadRequest("The provided token is not a refresh token"); - if (token is null) return LogicResult>.NotFound("Refresh token not valid"); + if (token.Type != Token.RefreshTokenType) + return LogicResult>.Conflict("The provided token is not a refresh token"); + if (token.CreatedAt + HopFrameAuthentication.RefreshTokenTime < DateTime.Now) - return LogicResult>.Conflict("Refresh token is expired"); + return LogicResult>.Forbidden("Refresh token is expired"); var accessToken = await tokens.CreateToken(Token.AccessTokenType, token.Owner); diff --git a/tests/HopFrame.Api.Tests/AuthLogicTests.cs b/tests/HopFrame.Api.Tests/AuthLogicTests.cs new file mode 100644 index 0000000..ae82b65 --- /dev/null +++ b/tests/HopFrame.Api.Tests/AuthLogicTests.cs @@ -0,0 +1,403 @@ +using System.Net; +using System.Security.Claims; +using HopFrame.Api.Logic; +using HopFrame.Api.Logic.Implementation; +using HopFrame.Api.Models; +using HopFrame.Api.Tests.Extensions; +using HopFrame.Database.Models; +using HopFrame.Database.Repositories; +using HopFrame.Security.Authentication; +using HopFrame.Security.Claims; +using HopFrame.Security.Models; +using Microsoft.AspNetCore.Http; +using Moq; + +namespace HopFrame.Api.Tests; + +public class AuthLogicTests { + + private readonly Guid _refreshToken = Guid.NewGuid(); + private readonly Guid _accessToken = Guid.NewGuid(); + + private (IAuthLogic, HttpContext) SetupEnvironment(bool passwordIsCorrect = true, Token providedRefreshToken = null, string providedTokenCookie = null, bool provideAccessToken = true) { + var accessor = new HttpContextAccessor { + HttpContext = new DefaultHttpContext() + }; + + if (providedTokenCookie != null) { + var cookies = new Mock(); + cookies + .SetupGet(c => c[ITokenContext.RefreshTokenType]) + .Returns(providedTokenCookie); + accessor.HttpContext.Request.Cookies = cookies.Object; + } + + if (provideAccessToken) { + var claims = new List { + new(HopFrameClaimTypes.AccessTokenId, _accessToken.ToString()) + }; + accessor.HttpContext.User.AddIdentity(new ClaimsIdentity(claims, HopFrameAuthentication.SchemeName)); + } + + var users = new Mock(); + users + .Setup(u => u.GetUserByEmail(It.Is(email => CreateDummyUser().Email == email))) + .ReturnsAsync(CreateDummyUser()); + users + .Setup(u => u.CheckUserPassword(It.Is(u => u.Email == CreateDummyUser().Email), It.IsAny())) + .ReturnsAsync(passwordIsCorrect); + users + .Setup(u => u.AddUser(It.IsAny())) + .ReturnsAsync(CreateDummyUser()); + users + .Setup(u => u.GetUsers()) + .ReturnsAsync(new List { CreateDummyUser() }); + + var tokens = new Mock(); + tokens + .Setup(t => t.CreateToken(It.Is(t => t == Token.RefreshTokenType), It.IsAny())) + .ReturnsAsync(new Token { + Content = _refreshToken, + Type = Token.RefreshTokenType + }); + tokens + .Setup(t => t.CreateToken(It.Is(t => t == Token.AccessTokenType), It.IsAny())) + .ReturnsAsync(new Token { + Content = _accessToken, + Type = Token.AccessTokenType + }); + tokens + .Setup(t => t.GetToken(It.Is(token => token == _refreshToken.ToString()))) + .ReturnsAsync(providedRefreshToken); + + var context = new Mock(); + context + .Setup(c => c.User) + .Returns(CreateDummyUser()); + + return (new AuthLogic(users.Object, tokens.Object, context.Object, accessor), accessor.HttpContext); + } + + private User CreateDummyUser() => new() { + Id = Guid.NewGuid(), + CreatedAt = DateTime.Now, + Email = "test@example.com", + Username = "ExampleUser", + Password = "1234567890" + }; + + [Fact] + public async Task Login_Should_Succeed() { + // Arrange + var (auth, context) = SetupEnvironment(); + var login = new UserLogin { + Email = CreateDummyUser().Email, + Password = CreateDummyUser().Password + }; + + // Act + var result = await auth.Login(login); + + // Assert + Assert.True(result.IsSuccessful); + Assert.Equal(_accessToken.ToString(), result.Data.Value); + Assert.Equal(_accessToken.ToString(), context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + Assert.Equal(_refreshToken.ToString(), context.Response.Headers.FindCookie(ITokenContext.RefreshTokenType)); + } + + [Fact] + public async Task Login_With_WrongEmail_Should_Fail() { + // Arrange + var (auth, context) = SetupEnvironment(); + var login = new UserLogin { + Email = "wrong@example.com", + Password = CreateDummyUser().Password + }; + + // Act + var result = await auth.Login(login); + + // Assert + Assert.False(result.IsSuccessful); + Assert.Equal(HttpStatusCode.NotFound, result.State); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.RefreshTokenType)); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + } + + [Fact] + public async Task Login_With_WrongPassword_Should_Fail() { + // Arrange + var (auth, context) = SetupEnvironment(false); + var login = new UserLogin { + Email = CreateDummyUser().Email, + Password = CreateDummyUser().Password + }; + + // Act + var result = await auth.Login(login); + + // Assert + Assert.False(result.IsSuccessful); + Assert.Equal(HttpStatusCode.Forbidden, result.State); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.RefreshTokenType)); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + } + + [Fact] + public async Task Register_Should_Succeed() { + // Arrange + var (auth, context) = SetupEnvironment(); + var register = new UserRegister { + Email = "new@example.com", + Password = "1234567890", + Username = "NewUser" + }; + + // Act + var result = await auth.Register(register); + + // Assert + Assert.True(result.IsSuccessful); + Assert.Equal(_accessToken.ToString(), result.Data.Value); + Assert.Equal(_accessToken.ToString(), context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + Assert.Equal(_refreshToken.ToString(), context.Response.Headers.FindCookie(ITokenContext.RefreshTokenType)); + } + + [Fact] + public async Task Register_With_ShortPassword_Should_Fail() { + // Arrange + var (auth, context) = SetupEnvironment(); + var register = new UserRegister { + Email = "new@example.com", + Password = "12345", + Username = "NewUser" + }; + + // Act + var result = await auth.Register(register); + + // Assert + Assert.False(result.IsSuccessful); + Assert.Equal(HttpStatusCode.BadRequest, result.State); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.RefreshTokenType)); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + } + + [Fact] + public async Task Register_With_ExistingUsername_Should_Fail() { + // Arrange + var (auth, context) = SetupEnvironment(); + var register = new UserRegister { + Email = "new@example.com", + Password = "1234567890", + Username = CreateDummyUser().Username + }; + + // Act + var result = await auth.Register(register); + + // Assert + Assert.False(result.IsSuccessful); + Assert.Equal(HttpStatusCode.Conflict, result.State); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.RefreshTokenType)); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + } + + [Fact] + public async Task Register_With_ExistingEmail_Should_Fail() { + // Arrange + var (auth, context) = SetupEnvironment(); + var register = new UserRegister { + Email = CreateDummyUser().Email, + Password = "1234567890", + Username = "NewUser" + }; + + // Act + var result = await auth.Register(register); + + // Assert + Assert.False(result.IsSuccessful); + Assert.Equal(HttpStatusCode.Conflict, result.State); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.RefreshTokenType)); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + } + + [Fact] + public async Task Authenticate_Should_Succeed() { + // Arrange + var token = new Token { + Type = Token.RefreshTokenType, + Content = _refreshToken, + CreatedAt = DateTime.Now, + Owner = CreateDummyUser() + }; + var (auth, context) = SetupEnvironment(true, token, token.Content.ToString()); + + // Act + var result = await auth.Authenticate(); + + // Assert + Assert.True(result.IsSuccessful); + Assert.Equal(_accessToken.ToString(), result.Data.Value); + Assert.Equal(_accessToken.ToString(), context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + } + + [Fact] + public async Task Authenticate_With_NoProvidedToken_Should_Fail() { + // Arrange + var (auth, context) = SetupEnvironment(); + + // Act + var result = await auth.Authenticate(); + + // Assert + Assert.False(result.IsSuccessful); + Assert.Equal(HttpStatusCode.BadRequest, result.State); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + } + + [Fact] + public async Task Authenticate_With_WrongToken_Should_Fail() { + // Arrange + var (auth, context) = SetupEnvironment(true, null, _refreshToken.ToString()); + + // Act + var result = await auth.Authenticate(); + + // Assert + Assert.False(result.IsSuccessful); + Assert.Equal(HttpStatusCode.NotFound, result.State); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + } + + [Fact] + public async Task Authenticate_With_WrongTokenType_Should_Fail() { + // Arrange + var token = new Token { + Type = Token.AccessTokenType, + Content = _refreshToken, + CreatedAt = DateTime.Now, + Owner = CreateDummyUser() + }; + var (auth, context) = SetupEnvironment(true, token, token.Content.ToString()); + + // Act + var result = await auth.Authenticate(); + + // Assert + Assert.False(result.IsSuccessful); + Assert.Equal(HttpStatusCode.Conflict, result.State); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + } + + [Fact] + public async Task Authenticate_With_ExpiredToken_Should_Fail() { + // Arrange + var token = new Token { + Type = Token.RefreshTokenType, + Content = _refreshToken, + CreatedAt = DateTime.MinValue, + Owner = CreateDummyUser() + }; + var (auth, context) = SetupEnvironment(true, token, token.Content.ToString()); + + // Act + var result = await auth.Authenticate(); + + // Assert + Assert.False(result.IsSuccessful); + Assert.Equal(HttpStatusCode.Forbidden, result.State); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + } + + [Fact] + public async Task Logout_Should_Succeed() { + // Arrange + var (auth, context) = SetupEnvironment(providedTokenCookie: _refreshToken.ToString()); + context.Response.Cookies.Append(ITokenContext.AccessTokenType, _accessToken.ToString()); + context.Response.Cookies.Append(ITokenContext.RefreshTokenType, _refreshToken.ToString()); + + // Act + var result = await auth.Logout(); + + // Assert + Assert.True(result.IsSuccessful); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.RefreshTokenType)); + } + + [Fact] + public async Task Logout_With_NoAccessToken_Should_Fail() { + // Arrange + var (auth, context) = SetupEnvironment(provideAccessToken: false); + context.Response.Cookies.Append(ITokenContext.AccessTokenType, _accessToken.ToString()); + context.Response.Cookies.Append(ITokenContext.RefreshTokenType, _refreshToken.ToString()); + + // Act + var result = await auth.Logout(); + + // Assert + Assert.False(result.IsSuccessful); + Assert.Equal(HttpStatusCode.Conflict, result.State); + Assert.Equal(_accessToken.ToString(), context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + Assert.Equal(_refreshToken.ToString(), context.Response.Headers.FindCookie(ITokenContext.RefreshTokenType)); + } + + [Fact] + public async Task Logout_With_NoRefreshToken_Should_Fail() { + // Arrange + var (auth, context) = SetupEnvironment(); + context.Response.Cookies.Append(ITokenContext.AccessTokenType, _accessToken.ToString()); + context.Response.Cookies.Append(ITokenContext.RefreshTokenType, _refreshToken.ToString()); + + // Act + var result = await auth.Logout(); + + // Assert + Assert.False(result.IsSuccessful); + Assert.Equal(HttpStatusCode.Conflict, result.State); + Assert.Equal(_accessToken.ToString(), context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + Assert.Equal(_refreshToken.ToString(), context.Response.Headers.FindCookie(ITokenContext.RefreshTokenType)); + } + + [Fact] + public async Task Delete_Should_Succeed() { + // Arrange + var (auth, context) = SetupEnvironment(); + context.Response.Cookies.Append(ITokenContext.AccessTokenType, _accessToken.ToString()); + context.Response.Cookies.Append(ITokenContext.RefreshTokenType, _refreshToken.ToString()); + var validation = new UserPasswordValidation { + Password = CreateDummyUser().Password + }; + + // Act + var result = await auth.Delete(validation); + + // Assert + Assert.True(result.IsSuccessful); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.RefreshTokenType)); + } + + [Fact] + public async Task Delete_With_WrongPassword_Should_Fail() { + // Arrange + var (auth, context) = SetupEnvironment(false); + context.Response.Cookies.Append(ITokenContext.AccessTokenType, _accessToken.ToString()); + context.Response.Cookies.Append(ITokenContext.RefreshTokenType, _refreshToken.ToString()); + var validation = new UserPasswordValidation { + Password = CreateDummyUser().Password + }; + + // Act + var result = await auth.Delete(validation); + + // Assert + Assert.False(result.IsSuccessful); + Assert.Equal(HttpStatusCode.Forbidden, result.State); + Assert.Equal(_accessToken.ToString(), context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + Assert.Equal(_refreshToken.ToString(), context.Response.Headers.FindCookie(ITokenContext.RefreshTokenType)); + } + +} \ No newline at end of file diff --git a/tests/HopFrame.Api.Tests/Extensions/HttpContextExtensions.cs b/tests/HopFrame.Api.Tests/Extensions/HttpContextExtensions.cs new file mode 100644 index 0000000..4ccbb38 --- /dev/null +++ b/tests/HopFrame.Api.Tests/Extensions/HttpContextExtensions.cs @@ -0,0 +1,29 @@ +using System.Web; +using Microsoft.AspNetCore.Http; + +namespace HopFrame.Api.Tests.Extensions; + +internal static class HttpContextExtensions { + /// Extracts the partial cookie value from the header section. + /// + /// The key for identifying the cookie. + /// The value of the cookie. + public static string FindCookie(this IHeaderDictionary headers, string key) + { + string headerKey = $"{key}="; + var cookies = headers.Values + .SelectMany(h => h) + .Where(header => header.StartsWith(headerKey)) + .Select(header => header.Substring(headerKey.Length).Split(';').First()) + .ToArray(); + + //Note: cookie values in a header are encoded like a uri parameter value. + var value = cookies.LastOrDefault();//and the last set value, is the relevant one. + if (string.IsNullOrEmpty(value)) + return null; + + //That's why we should decode that last value, before we return it. + var decoded = HttpUtility.UrlDecode(value); + return decoded; + } +} \ No newline at end of file diff --git a/tests/HopFrame.Api.Tests/HopFrame.Api.Tests.csproj b/tests/HopFrame.Api.Tests/HopFrame.Api.Tests.csproj new file mode 100644 index 0000000..6c7f59f --- /dev/null +++ b/tests/HopFrame.Api.Tests/HopFrame.Api.Tests.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + enable + disable + + false + true + + + + + + + + + + + + + + + + + + + From 4d91ce181987796fd8a9baaab9b218cbec51e602 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Tue, 10 Dec 2024 16:30:46 +0100 Subject: [PATCH 08/12] Implemented HopFrame.Web tests --- HopFrame.sln | 7 + src/HopFrame.Web/HopFrame.Web.csproj | 6 + .../Services/Implementation/AuthService.cs | 13 +- .../HopFrame.Web.Tests/AuthMiddlewareTests.cs | 94 +++++ tests/HopFrame.Web.Tests/AuthServiceTests.cs | 334 ++++++++++++++++++ .../Extensions/HttpContextExtensions.cs | 29 ++ .../HopFrame.Web.Tests.csproj | 28 ++ 7 files changed, 503 insertions(+), 8 deletions(-) create mode 100644 tests/HopFrame.Web.Tests/AuthMiddlewareTests.cs create mode 100644 tests/HopFrame.Web.Tests/AuthServiceTests.cs create mode 100644 tests/HopFrame.Web.Tests/Extensions/HttpContextExtensions.cs create mode 100644 tests/HopFrame.Web.Tests/HopFrame.Web.Tests.csproj diff --git a/HopFrame.sln b/HopFrame.sln index b5779b1..195f022 100644 --- a/HopFrame.sln +++ b/HopFrame.sln @@ -26,6 +26,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Security.Tests", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Api.Tests", "tests\HopFrame.Api.Tests\HopFrame.Api.Tests.csproj", "{25DE1510-47E5-46FF-89A4-B9F99542218E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Web.Tests", "tests\HopFrame.Web.Tests\HopFrame.Web.Tests.csproj", "{566C13B9-4ECA-48C4-8D02-FEB6CDF523E6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -72,6 +74,10 @@ Global {25DE1510-47E5-46FF-89A4-B9F99542218E}.Debug|Any CPU.Build.0 = Debug|Any CPU {25DE1510-47E5-46FF-89A4-B9F99542218E}.Release|Any CPU.ActiveCfg = Release|Any CPU {25DE1510-47E5-46FF-89A4-B9F99542218E}.Release|Any CPU.Build.0 = Release|Any CPU + {566C13B9-4ECA-48C4-8D02-FEB6CDF523E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {566C13B9-4ECA-48C4-8D02-FEB6CDF523E6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {566C13B9-4ECA-48C4-8D02-FEB6CDF523E6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {566C13B9-4ECA-48C4-8D02-FEB6CDF523E6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {1E821490-AEDC-4F55-B758-52F4FADAB53A} = {64EDCBED-A84F-4936-8697-78DC43CB2427} @@ -84,5 +90,6 @@ Global {1CAAC943-B8FE-48DD-9712-92699647DE18} = {1D98E5DE-CB8B-4C1C-A319-D49AC137441A} {6747753A-6059-48F1-B779-D73765A373A6} = {1D98E5DE-CB8B-4C1C-A319-D49AC137441A} {25DE1510-47E5-46FF-89A4-B9F99542218E} = {1D98E5DE-CB8B-4C1C-A319-D49AC137441A} + {566C13B9-4ECA-48C4-8D02-FEB6CDF523E6} = {1D98E5DE-CB8B-4C1C-A319-D49AC137441A} EndGlobalSection EndGlobal diff --git a/src/HopFrame.Web/HopFrame.Web.csproj b/src/HopFrame.Web/HopFrame.Web.csproj index a512b95..c368805 100644 --- a/src/HopFrame.Web/HopFrame.Web.csproj +++ b/src/HopFrame.Web/HopFrame.Web.csproj @@ -35,4 +35,10 @@ + + + <_Parameter1>HopFrame.Web.Tests + + + diff --git a/src/HopFrame.Web/Services/Implementation/AuthService.cs b/src/HopFrame.Web/Services/Implementation/AuthService.cs index 6a96a97..e5f1ec0 100644 --- a/src/HopFrame.Web/Services/Implementation/AuthService.cs +++ b/src/HopFrame.Web/Services/Implementation/AuthService.cs @@ -91,15 +91,12 @@ internal class AuthService( } public async Task IsLoggedIn() { - var accessToken = httpAccessor.HttpContext?.Request.Cookies[ITokenContext.AccessTokenType]; - if (string.IsNullOrEmpty(accessToken)) return false; - - var tokenEntry = await tokens.GetToken(accessToken); + var accessToken = context.AccessToken; - if (tokenEntry is null) return false; - if (tokenEntry.Type != Token.AccessTokenType) return false; - if (tokenEntry.CreatedAt + HopFrameAuthentication.AccessTokenTime < DateTime.Now) return false; - if (tokenEntry.Owner is null) return false; + if (accessToken is null) return false; + if (accessToken.Type != Token.AccessTokenType) return false; + if (accessToken.CreatedAt + HopFrameAuthentication.AccessTokenTime < DateTime.Now) return false; + if (accessToken.Owner is null) return false; return true; } diff --git a/tests/HopFrame.Web.Tests/AuthMiddlewareTests.cs b/tests/HopFrame.Web.Tests/AuthMiddlewareTests.cs new file mode 100644 index 0000000..70d1f8e --- /dev/null +++ b/tests/HopFrame.Web.Tests/AuthMiddlewareTests.cs @@ -0,0 +1,94 @@ +using System.Security.Claims; +using HopFrame.Database.Models; +using HopFrame.Database.Repositories; +using HopFrame.Security.Claims; +using HopFrame.Web.Services; +using Microsoft.AspNetCore.Http; +using Moq; + +namespace HopFrame.Web.Tests; + +public class AuthMiddlewareTests { + private readonly RequestDelegate _delegate = _ => Task.CompletedTask; + + public AuthMiddleware SetupEnvironment(bool isLoggedIn = true, Token newToken = null) { + var auth = new Mock(); + auth + .Setup(a => a.IsLoggedIn()) + .ReturnsAsync(isLoggedIn); + auth + .Setup(a => a.RefreshLogin()) + .ReturnsAsync(newToken); + + var perms = new Mock(); + perms + .Setup(p => p.GetFullPermissions(It.Is(u => newToken.Owner.Id == u.Id))) + .ReturnsAsync(CreateDummyUser().Permissions.Select(p => p.PermissionName).ToList); + + return new AuthMiddleware(auth.Object, perms.Object); + } + + private User CreateDummyUser() => new() { + Id = Guid.NewGuid(), + CreatedAt = DateTime.Now, + Email = "test@example.com", + Username = "ExampleUser", + Password = "1234567890", + Permissions = new List { + new () { + PermissionName = "test.permission" + } + } + }; + + [Fact] + public async Task InvokeAsync_With_ValidLogin_Should_Succeed() { + // Arrange + var auth = SetupEnvironment(); + var context = new DefaultHttpContext(); + + // Act + await auth.InvokeAsync(context, _delegate); + + // Assert + Assert.Null(context.User.FindFirst(HopFrameClaimTypes.UserId)); + Assert.Null(context.User.FindFirst(HopFrameClaimTypes.AccessTokenId)); + Assert.Null(context.User.FindFirst(HopFrameClaimTypes.Permission)); + } + + [Fact] + public async Task InvokeAsync_With_InvalidLoginValidToken_Should_Succeed() { + // Arrange + var token = new Token { + Content = Guid.NewGuid(), + CreatedAt = DateTime.Now, + Type = Token.AccessTokenType, + Owner = CreateDummyUser() + }; + var auth = SetupEnvironment(false, token); + var context = new DefaultHttpContext(); + + // Act + await auth.InvokeAsync(context, _delegate); + + // Assert + Assert.Equal(token.Owner.Id.ToString(), context.User.FindFirstValue(HopFrameClaimTypes.UserId)); + Assert.Equal(token.Content.ToString(), context.User.FindFirstValue(HopFrameClaimTypes.AccessTokenId)); + Assert.Equal(token.Owner.Permissions.First().PermissionName, context.User.FindFirstValue(HopFrameClaimTypes.Permission)); + } + + [Fact] + public async Task InvokeAsync_With_InvalidLoginInvalidToken_Should_Succeed() { + // Arrange + var auth = SetupEnvironment(false); + var context = new DefaultHttpContext(); + + // Act + await auth.InvokeAsync(context, _delegate); + + // Assert + Assert.Null(context.User.FindFirst(HopFrameClaimTypes.UserId)); + Assert.Null(context.User.FindFirst(HopFrameClaimTypes.AccessTokenId)); + Assert.Null(context.User.FindFirst(HopFrameClaimTypes.Permission)); + } +} \ No newline at end of file diff --git a/tests/HopFrame.Web.Tests/AuthServiceTests.cs b/tests/HopFrame.Web.Tests/AuthServiceTests.cs new file mode 100644 index 0000000..4374148 --- /dev/null +++ b/tests/HopFrame.Web.Tests/AuthServiceTests.cs @@ -0,0 +1,334 @@ +using HopFrame.Api.Tests.Extensions; +using HopFrame.Database.Models; +using HopFrame.Database.Repositories; +using HopFrame.Security.Claims; +using HopFrame.Security.Models; +using HopFrame.Web.Services; +using HopFrame.Web.Services.Implementation; +using Microsoft.AspNetCore.Http; +using Moq; + +namespace HopFrame.Web.Tests; + +public class AuthServiceTests { + private readonly Guid _refreshToken = Guid.NewGuid(); + private readonly Guid _accessToken = Guid.NewGuid(); + + private (IAuthService, HttpContext) SetupEnvironment(bool passwordIsCorrect = true, Token providedRefreshToken = null, string providedTokenCookie = null, Token providedAccessToken = null) { + var accessor = new HttpContextAccessor { + HttpContext = new DefaultHttpContext() + }; + + if (providedTokenCookie != null) { + var cookies = new Mock(); + cookies + .SetupGet(c => c[ITokenContext.RefreshTokenType]) + .Returns(providedTokenCookie); + accessor.HttpContext.Request.Cookies = cookies.Object; + } + + var users = new Mock(); + users + .Setup(u => u.GetUserByEmail(It.Is(email => CreateDummyUser().Email == email))) + .ReturnsAsync(CreateDummyUser()); + users + .Setup(u => u.CheckUserPassword(It.Is(u => u.Email == CreateDummyUser().Email), It.IsAny())) + .ReturnsAsync(passwordIsCorrect); + users + .Setup(u => u.AddUser(It.IsAny())) + .ReturnsAsync(CreateDummyUser()); + users + .Setup(u => u.GetUsers()) + .ReturnsAsync(new List { CreateDummyUser() }); + + var tokens = new Mock(); + tokens + .Setup(t => t.CreateToken(It.Is(t => t == Token.RefreshTokenType), It.IsAny())) + .ReturnsAsync(new Token { + Content = _refreshToken, + Type = Token.RefreshTokenType + }); + tokens + .Setup(t => t.CreateToken(It.Is(t => t == Token.AccessTokenType), It.IsAny())) + .ReturnsAsync(new Token { + Content = _accessToken, + Type = Token.AccessTokenType + }); + tokens + .Setup(t => t.GetToken(It.Is(token => token == _refreshToken.ToString()))) + .ReturnsAsync(providedRefreshToken); + + var context = new Mock(); + context + .Setup(c => c.User) + .Returns(CreateDummyUser()); + context + .Setup(c => c.AccessToken) + .Returns(providedAccessToken); + + return (new AuthService(users.Object, accessor, tokens.Object, context.Object), accessor.HttpContext); + } + + private User CreateDummyUser() => new() { + Id = Guid.NewGuid(), + CreatedAt = DateTime.Now, + Email = "test@example.com", + Username = "ExampleUser", + Password = "1234567890" + }; + + [Fact] + public async Task Register_Should_Succeed() { + // Arrange + var (service, context) = SetupEnvironment(); + var register = new UserRegister { + Email = CreateDummyUser().Email, + Username = CreateDummyUser().Username, + Password = CreateDummyUser().Password + }; + + // Act + await service.Register(register); + + // Assert + Assert.Equal(_accessToken.ToString(), context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + Assert.Equal(_refreshToken.ToString(), context.Response.Headers.FindCookie(ITokenContext.RefreshTokenType)); + } + + [Fact] + public async Task Login_Should_Succeed() { + // Arrange + var (service, context) = SetupEnvironment(); + var login = new UserLogin { + Email = CreateDummyUser().Email, + Password = CreateDummyUser().Password + }; + + // Act + var result = await service.Login(login); + + // Assert + Assert.True(result); + Assert.Equal(_accessToken.ToString(), context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + Assert.Equal(_refreshToken.ToString(), context.Response.Headers.FindCookie(ITokenContext.RefreshTokenType)); + } + + [Fact] + public async Task Login_With_WrongPassword_Should_Fail() { + // Arrange + var (service, context) = SetupEnvironment(false); + var login = new UserLogin { + Email = CreateDummyUser().Email, + Password = CreateDummyUser().Password + }; + + // Act + var result = await service.Login(login); + + // Assert + Assert.False(result); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.RefreshTokenType)); + } + + [Fact] + public async Task Login_With_WrongEmail_Should_Fail() { + // Arrange + var (service, context) = SetupEnvironment(); + var login = new UserLogin { + Email = "wrong@example.com", + Password = CreateDummyUser().Password + }; + + // Act + var result = await service.Login(login); + + // Assert + Assert.False(result); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.RefreshTokenType)); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + } + + [Fact] + public async Task Logout_Should_Succeed() { + // Arrange + var (service, context) = SetupEnvironment(providedTokenCookie: _refreshToken.ToString()); + context.Response.Cookies.Append(ITokenContext.AccessTokenType, _accessToken.ToString()); + context.Response.Cookies.Append(ITokenContext.RefreshTokenType, _refreshToken.ToString()); + + // Act + await service.Logout(); + + // Assert + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.RefreshTokenType)); + } + + [Fact] + public async Task RefreshLogin_Should_Succeed() { + // Arrange + var token = new Token { + Type = Token.RefreshTokenType, + Content = _refreshToken, + CreatedAt = DateTime.Now, + Owner = CreateDummyUser() + }; + var (service, context) = SetupEnvironment(true, token, token.Content.ToString()); + + // Act + var result = await service.RefreshLogin(); + + // Assert + Assert.NotNull(result); + Assert.Equal(_accessToken, result.Content); + Assert.Equal(_accessToken.ToString(), context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + } + + [Fact] + public async Task RefreshLogin_With_NoProvidedToken_Should_Fail() { + // Arrange + var (service, context) = SetupEnvironment(); + + // Act + var result = await service.RefreshLogin(); + + // Assert + Assert.Null(result); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + } + + [Fact] + public async Task RefreshLogin_With_WrongToken_Should_Fail() { + // Arrange + var (service, context) = SetupEnvironment(true, null, _refreshToken.ToString()); + + // Act + var result = await service.RefreshLogin(); + + // Assert + Assert.Null(result); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + } + + [Fact] + public async Task RefreshLogin_With_WrongTokenType_Should_Fail() { + // Arrange + var token = new Token { + Type = Token.AccessTokenType, + Content = _refreshToken, + CreatedAt = DateTime.Now, + Owner = CreateDummyUser() + }; + var (service, context) = SetupEnvironment(true, token, token.Content.ToString()); + + // Act + var result = await service.RefreshLogin(); + + // Assert + Assert.Null(result); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + } + + [Fact] + public async Task RefreshLogin_With_ExpiredToken_Should_Fail() { + // Arrange + var token = new Token { + Type = Token.RefreshTokenType, + Content = _refreshToken, + CreatedAt = DateTime.MinValue, + Owner = CreateDummyUser() + }; + var (service, context) = SetupEnvironment(true, token, token.Content.ToString()); + + // Act + var result = await service.RefreshLogin(); + + // Assert + Assert.Null(result); + Assert.Null(context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); + } + + [Fact] + public async Task IsLoggedIn_Should_Succeed() { + // Arrange + var token = new Token { + Type = Token.AccessTokenType, + Content = _accessToken, + CreatedAt = DateTime.Now, + Owner = CreateDummyUser() + }; + var (service, context) = SetupEnvironment(providedAccessToken: token); + + // Act + var result = await service.IsLoggedIn(); + + // Assert + Assert.True(result); + } + + [Fact] + public async Task IsLoggedIn_With_NoProvidedToken_Should_Fail() { + // Arrange + var (service, context) = SetupEnvironment(); + + // Act + var result = await service.IsLoggedIn(); + + // Assert + Assert.False(result); + } + + [Fact] + public async Task IsLoggedIn_With_WrongTokenType_Should_Fail() { + // Arrange + var token = new Token { + Type = Token.RefreshTokenType, + Content = _accessToken, + CreatedAt = DateTime.Now, + Owner = CreateDummyUser() + }; + var (service, context) = SetupEnvironment(providedAccessToken: token); + + // Act + var result = await service.IsLoggedIn(); + + // Assert + Assert.False(result); + } + + [Fact] + public async Task IsLoggedIn_With_ExpiredToken_Should_Fail() { + // Arrange + var token = new Token { + Type = Token.AccessTokenType, + Content = _accessToken, + CreatedAt = DateTime.MinValue, + Owner = CreateDummyUser() + }; + var (service, context) = SetupEnvironment(providedAccessToken: token); + + // Act + var result = await service.IsLoggedIn(); + + // Assert + Assert.False(result); + } + + [Fact] + public async Task IsLoggedIn_With_NoOwner_Should_Fail() { + // Arrange + var token = new Token { + Type = Token.AccessTokenType, + Content = _accessToken, + CreatedAt = DateTime.Now, + Owner = null + }; + var (service, context) = SetupEnvironment(providedAccessToken: token); + + // Act + var result = await service.IsLoggedIn(); + + // Assert + Assert.False(result); + } +} \ No newline at end of file diff --git a/tests/HopFrame.Web.Tests/Extensions/HttpContextExtensions.cs b/tests/HopFrame.Web.Tests/Extensions/HttpContextExtensions.cs new file mode 100644 index 0000000..4ccbb38 --- /dev/null +++ b/tests/HopFrame.Web.Tests/Extensions/HttpContextExtensions.cs @@ -0,0 +1,29 @@ +using System.Web; +using Microsoft.AspNetCore.Http; + +namespace HopFrame.Api.Tests.Extensions; + +internal static class HttpContextExtensions { + /// Extracts the partial cookie value from the header section. + /// + /// The key for identifying the cookie. + /// The value of the cookie. + public static string FindCookie(this IHeaderDictionary headers, string key) + { + string headerKey = $"{key}="; + var cookies = headers.Values + .SelectMany(h => h) + .Where(header => header.StartsWith(headerKey)) + .Select(header => header.Substring(headerKey.Length).Split(';').First()) + .ToArray(); + + //Note: cookie values in a header are encoded like a uri parameter value. + var value = cookies.LastOrDefault();//and the last set value, is the relevant one. + if (string.IsNullOrEmpty(value)) + return null; + + //That's why we should decode that last value, before we return it. + var decoded = HttpUtility.UrlDecode(value); + return decoded; + } +} \ No newline at end of file diff --git a/tests/HopFrame.Web.Tests/HopFrame.Web.Tests.csproj b/tests/HopFrame.Web.Tests/HopFrame.Web.Tests.csproj new file mode 100644 index 0000000..062ade0 --- /dev/null +++ b/tests/HopFrame.Web.Tests/HopFrame.Web.Tests.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + enable + disable + + false + true + + + + + + + + + + + + + + + + + + + From ee7bf1e204710021183590f26d744a0f482d8f1f Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Tue, 10 Dec 2024 16:39:28 +0100 Subject: [PATCH 09/12] Renamed test projects --- HopFrame.sln | 8 ++++---- src/HopFrame.Api/HopFrame.Api.csproj | 2 +- src/HopFrame.Database/HopFrame.Database.csproj | 2 +- src/HopFrame.Web/HopFrame.Web.csproj | 2 +- .../AuthLogicTests.cs | 4 ++-- .../Extensions/HttpContextExtensions.cs | 2 +- .../HopFrame.Tests.Api.csproj} | 0 .../Data/DatabaseContext.cs | 3 ++- .../HopFrame.Tests.Database.csproj} | 0 .../PermissionValidatorTests.cs | 4 +++- .../Repositories/GroupRepositoryTests.cs | 4 ++-- .../Repositories/PermissionRepositoryTests.cs | 4 ++-- .../Repositories/TokenRepositoryTests.cs | 4 ++-- .../Repositories/UserRepositoryTests.cs | 4 ++-- .../AuthenticationTests.cs | 2 +- .../AuthorizationTests.cs | 4 ++-- .../HopFrame.Tests.Security.csproj} | 0 .../AuthMiddlewareTests.cs | 3 ++- .../AuthServiceTests.cs | 4 ++-- .../Extensions/HttpContextExtensions.cs | 2 +- .../HopFrame.Tests.Web.csproj} | 0 21 files changed, 31 insertions(+), 27 deletions(-) rename tests/{HopFrame.Api.Tests => HopFrame.Tests.Api}/AuthLogicTests.cs (99%) rename tests/{HopFrame.Api.Tests => HopFrame.Tests.Api}/Extensions/HttpContextExtensions.cs (96%) rename tests/{HopFrame.Api.Tests/HopFrame.Api.Tests.csproj => HopFrame.Tests.Api/HopFrame.Tests.Api.csproj} (100%) rename tests/{HopFrame.Database.Tests => HopFrame.Tests.Database}/Data/DatabaseContext.cs (81%) rename tests/{HopFrame.Database.Tests/HopFrame.Database.Tests.csproj => HopFrame.Tests.Database/HopFrame.Tests.Database.csproj} (100%) rename tests/{HopFrame.Database.Tests => HopFrame.Tests.Database}/PermissionValidatorTests.cs (96%) rename tests/{HopFrame.Database.Tests => HopFrame.Tests.Database}/Repositories/GroupRepositoryTests.cs (98%) rename tests/{HopFrame.Database.Tests => HopFrame.Tests.Database}/Repositories/PermissionRepositoryTests.cs (97%) rename tests/{HopFrame.Database.Tests => HopFrame.Tests.Database}/Repositories/TokenRepositoryTests.cs (96%) rename tests/{HopFrame.Database.Tests => HopFrame.Tests.Database}/Repositories/UserRepositoryTests.cs (98%) rename tests/{HopFrame.Security.Tests => HopFrame.Tests.Security}/AuthenticationTests.cs (99%) rename tests/{HopFrame.Security.Tests => HopFrame.Tests.Security}/AuthorizationTests.cs (98%) rename tests/{HopFrame.Security.Tests/HopFrame.Security.Tests.csproj => HopFrame.Tests.Security/HopFrame.Tests.Security.csproj} (100%) rename tests/{HopFrame.Web.Tests => HopFrame.Tests.Web}/AuthMiddlewareTests.cs (98%) rename tests/{HopFrame.Web.Tests => HopFrame.Tests.Web}/AuthServiceTests.cs (99%) rename tests/{HopFrame.Web.Tests => HopFrame.Tests.Web}/Extensions/HttpContextExtensions.cs (96%) rename tests/{HopFrame.Web.Tests/HopFrame.Web.Tests.csproj => HopFrame.Tests.Web/HopFrame.Tests.Web.csproj} (100%) diff --git a/HopFrame.sln b/HopFrame.sln index 195f022..95067c5 100644 --- a/HopFrame.sln +++ b/HopFrame.sln @@ -20,13 +20,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Testing", "Testing", "{EEA2 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{1D98E5DE-CB8B-4C1C-A319-D49AC137441A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Database.Tests", "tests\HopFrame.Database.Tests\HopFrame.Database.Tests.csproj", "{1CAAC943-B8FE-48DD-9712-92699647DE18}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Tests.Database", "tests\HopFrame.Tests.Database\HopFrame.Tests.Database.csproj", "{1CAAC943-B8FE-48DD-9712-92699647DE18}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Security.Tests", "tests\HopFrame.Security.Tests\HopFrame.Security.Tests.csproj", "{6747753A-6059-48F1-B779-D73765A373A6}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Tests.Security", "tests\HopFrame.Tests.Security\HopFrame.Tests.Security.csproj", "{6747753A-6059-48F1-B779-D73765A373A6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Api.Tests", "tests\HopFrame.Api.Tests\HopFrame.Api.Tests.csproj", "{25DE1510-47E5-46FF-89A4-B9F99542218E}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Tests.Api", "tests\HopFrame.Tests.Api\HopFrame.Tests.Api.csproj", "{25DE1510-47E5-46FF-89A4-B9F99542218E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Web.Tests", "tests\HopFrame.Web.Tests\HopFrame.Web.Tests.csproj", "{566C13B9-4ECA-48C4-8D02-FEB6CDF523E6}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Tests.Web", "tests\HopFrame.Tests.Web\HopFrame.Tests.Web.csproj", "{566C13B9-4ECA-48C4-8D02-FEB6CDF523E6}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/HopFrame.Api/HopFrame.Api.csproj b/src/HopFrame.Api/HopFrame.Api.csproj index c8b21c4..091e5a1 100644 --- a/src/HopFrame.Api/HopFrame.Api.csproj +++ b/src/HopFrame.Api/HopFrame.Api.csproj @@ -24,7 +24,7 @@ - <_Parameter1>HopFrame.Api.Tests + <_Parameter1>HopFrame.Tests.Api diff --git a/src/HopFrame.Database/HopFrame.Database.csproj b/src/HopFrame.Database/HopFrame.Database.csproj index 621c7b9..cd670af 100644 --- a/src/HopFrame.Database/HopFrame.Database.csproj +++ b/src/HopFrame.Database/HopFrame.Database.csproj @@ -24,7 +24,7 @@ - <_Parameter1>HopFrame.Database.Tests + <_Parameter1>HopFrame.Tests.Database diff --git a/src/HopFrame.Web/HopFrame.Web.csproj b/src/HopFrame.Web/HopFrame.Web.csproj index c368805..1eb0490 100644 --- a/src/HopFrame.Web/HopFrame.Web.csproj +++ b/src/HopFrame.Web/HopFrame.Web.csproj @@ -37,7 +37,7 @@ - <_Parameter1>HopFrame.Web.Tests + <_Parameter1>HopFrame.Tests.Web diff --git a/tests/HopFrame.Api.Tests/AuthLogicTests.cs b/tests/HopFrame.Tests.Api/AuthLogicTests.cs similarity index 99% rename from tests/HopFrame.Api.Tests/AuthLogicTests.cs rename to tests/HopFrame.Tests.Api/AuthLogicTests.cs index ae82b65..ca86b5b 100644 --- a/tests/HopFrame.Api.Tests/AuthLogicTests.cs +++ b/tests/HopFrame.Tests.Api/AuthLogicTests.cs @@ -3,7 +3,7 @@ using System.Security.Claims; using HopFrame.Api.Logic; using HopFrame.Api.Logic.Implementation; using HopFrame.Api.Models; -using HopFrame.Api.Tests.Extensions; +using HopFrame.Tests.Api.Extensions; using HopFrame.Database.Models; using HopFrame.Database.Repositories; using HopFrame.Security.Authentication; @@ -12,7 +12,7 @@ using HopFrame.Security.Models; using Microsoft.AspNetCore.Http; using Moq; -namespace HopFrame.Api.Tests; +namespace HopFrame.Tests.Api; public class AuthLogicTests { diff --git a/tests/HopFrame.Api.Tests/Extensions/HttpContextExtensions.cs b/tests/HopFrame.Tests.Api/Extensions/HttpContextExtensions.cs similarity index 96% rename from tests/HopFrame.Api.Tests/Extensions/HttpContextExtensions.cs rename to tests/HopFrame.Tests.Api/Extensions/HttpContextExtensions.cs index 4ccbb38..8f4de69 100644 --- a/tests/HopFrame.Api.Tests/Extensions/HttpContextExtensions.cs +++ b/tests/HopFrame.Tests.Api/Extensions/HttpContextExtensions.cs @@ -1,7 +1,7 @@ using System.Web; using Microsoft.AspNetCore.Http; -namespace HopFrame.Api.Tests.Extensions; +namespace HopFrame.Tests.Api.Extensions; internal static class HttpContextExtensions { /// Extracts the partial cookie value from the header section. diff --git a/tests/HopFrame.Api.Tests/HopFrame.Api.Tests.csproj b/tests/HopFrame.Tests.Api/HopFrame.Tests.Api.csproj similarity index 100% rename from tests/HopFrame.Api.Tests/HopFrame.Api.Tests.csproj rename to tests/HopFrame.Tests.Api/HopFrame.Tests.Api.csproj diff --git a/tests/HopFrame.Database.Tests/Data/DatabaseContext.cs b/tests/HopFrame.Tests.Database/Data/DatabaseContext.cs similarity index 81% rename from tests/HopFrame.Database.Tests/Data/DatabaseContext.cs rename to tests/HopFrame.Tests.Database/Data/DatabaseContext.cs index 7bc93de..271628f 100644 --- a/tests/HopFrame.Database.Tests/Data/DatabaseContext.cs +++ b/tests/HopFrame.Tests.Database/Data/DatabaseContext.cs @@ -1,6 +1,7 @@ +using HopFrame.Database; using Microsoft.EntityFrameworkCore; -namespace HopFrame.Database.Tests.Data; +namespace HopFrame.Tests.Database.Data; public class DatabaseContext : HopDbContextBase { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { diff --git a/tests/HopFrame.Database.Tests/HopFrame.Database.Tests.csproj b/tests/HopFrame.Tests.Database/HopFrame.Tests.Database.csproj similarity index 100% rename from tests/HopFrame.Database.Tests/HopFrame.Database.Tests.csproj rename to tests/HopFrame.Tests.Database/HopFrame.Tests.Database.csproj diff --git a/tests/HopFrame.Database.Tests/PermissionValidatorTests.cs b/tests/HopFrame.Tests.Database/PermissionValidatorTests.cs similarity index 96% rename from tests/HopFrame.Database.Tests/PermissionValidatorTests.cs rename to tests/HopFrame.Tests.Database/PermissionValidatorTests.cs index 9d858f8..693f093 100644 --- a/tests/HopFrame.Database.Tests/PermissionValidatorTests.cs +++ b/tests/HopFrame.Tests.Database/PermissionValidatorTests.cs @@ -1,4 +1,6 @@ -namespace HopFrame.Database.Tests; +using HopFrame.Database; + +namespace HopFrame.Tests.Database; public class PermissionValidatorTests { diff --git a/tests/HopFrame.Database.Tests/Repositories/GroupRepositoryTests.cs b/tests/HopFrame.Tests.Database/Repositories/GroupRepositoryTests.cs similarity index 98% rename from tests/HopFrame.Database.Tests/Repositories/GroupRepositoryTests.cs rename to tests/HopFrame.Tests.Database/Repositories/GroupRepositoryTests.cs index 84270e0..e3fd4ff 100644 --- a/tests/HopFrame.Database.Tests/Repositories/GroupRepositoryTests.cs +++ b/tests/HopFrame.Tests.Database/Repositories/GroupRepositoryTests.cs @@ -1,9 +1,9 @@ using HopFrame.Database.Models; using HopFrame.Database.Repositories; using HopFrame.Database.Repositories.Implementation; -using HopFrame.Database.Tests.Data; +using HopFrame.Tests.Database.Data; -namespace HopFrame.Database.Tests.Repositories; +namespace HopFrame.Tests.Database.Repositories; public class GroupRepositoryTests { diff --git a/tests/HopFrame.Database.Tests/Repositories/PermissionRepositoryTests.cs b/tests/HopFrame.Tests.Database/Repositories/PermissionRepositoryTests.cs similarity index 97% rename from tests/HopFrame.Database.Tests/Repositories/PermissionRepositoryTests.cs rename to tests/HopFrame.Tests.Database/Repositories/PermissionRepositoryTests.cs index 52a9ef7..79fff4c 100644 --- a/tests/HopFrame.Database.Tests/Repositories/PermissionRepositoryTests.cs +++ b/tests/HopFrame.Tests.Database/Repositories/PermissionRepositoryTests.cs @@ -1,10 +1,10 @@ using HopFrame.Database.Models; using HopFrame.Database.Repositories; using HopFrame.Database.Repositories.Implementation; -using HopFrame.Database.Tests.Data; +using HopFrame.Tests.Database.Data; using Microsoft.EntityFrameworkCore; -namespace HopFrame.Database.Tests.Repositories; +namespace HopFrame.Tests.Database.Repositories; public class PermissionRepositoryTests { diff --git a/tests/HopFrame.Database.Tests/Repositories/TokenRepositoryTests.cs b/tests/HopFrame.Tests.Database/Repositories/TokenRepositoryTests.cs similarity index 96% rename from tests/HopFrame.Database.Tests/Repositories/TokenRepositoryTests.cs rename to tests/HopFrame.Tests.Database/Repositories/TokenRepositoryTests.cs index 97e382a..83dc770 100644 --- a/tests/HopFrame.Database.Tests/Repositories/TokenRepositoryTests.cs +++ b/tests/HopFrame.Tests.Database/Repositories/TokenRepositoryTests.cs @@ -1,10 +1,10 @@ using HopFrame.Database.Models; using HopFrame.Database.Repositories; using HopFrame.Database.Repositories.Implementation; -using HopFrame.Database.Tests.Data; +using HopFrame.Tests.Database.Data; using Microsoft.EntityFrameworkCore; -namespace HopFrame.Database.Tests.Repositories; +namespace HopFrame.Tests.Database.Repositories; public class TokenRepositoryTests { diff --git a/tests/HopFrame.Database.Tests/Repositories/UserRepositoryTests.cs b/tests/HopFrame.Tests.Database/Repositories/UserRepositoryTests.cs similarity index 98% rename from tests/HopFrame.Database.Tests/Repositories/UserRepositoryTests.cs rename to tests/HopFrame.Tests.Database/Repositories/UserRepositoryTests.cs index 7d8362b..5730064 100644 --- a/tests/HopFrame.Database.Tests/Repositories/UserRepositoryTests.cs +++ b/tests/HopFrame.Tests.Database/Repositories/UserRepositoryTests.cs @@ -1,9 +1,9 @@ using HopFrame.Database.Models; using HopFrame.Database.Repositories; using HopFrame.Database.Repositories.Implementation; -using HopFrame.Database.Tests.Data; +using HopFrame.Tests.Database.Data; -namespace HopFrame.Database.Tests.Repositories; +namespace HopFrame.Tests.Database.Repositories; public class UserRepositoryTests { diff --git a/tests/HopFrame.Security.Tests/AuthenticationTests.cs b/tests/HopFrame.Tests.Security/AuthenticationTests.cs similarity index 99% rename from tests/HopFrame.Security.Tests/AuthenticationTests.cs rename to tests/HopFrame.Tests.Security/AuthenticationTests.cs index 2594cbb..7c80e7d 100644 --- a/tests/HopFrame.Security.Tests/AuthenticationTests.cs +++ b/tests/HopFrame.Tests.Security/AuthenticationTests.cs @@ -8,7 +8,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Moq; -namespace HopFrame.Security.Tests; +namespace HopFrame.Tests.Security; public class AuthenticationTests { diff --git a/tests/HopFrame.Security.Tests/AuthorizationTests.cs b/tests/HopFrame.Tests.Security/AuthorizationTests.cs similarity index 98% rename from tests/HopFrame.Security.Tests/AuthorizationTests.cs rename to tests/HopFrame.Tests.Security/AuthorizationTests.cs index 730dfde..98032fb 100644 --- a/tests/HopFrame.Security.Tests/AuthorizationTests.cs +++ b/tests/HopFrame.Tests.Security/AuthorizationTests.cs @@ -2,14 +2,14 @@ using System.Security.Claims; using HopFrame.Security.Authentication; using HopFrame.Security.Authorization; using HopFrame.Security.Claims; -using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Routing; using Moq; -namespace HopFrame.Security.Tests; +namespace HopFrame.Tests.Security; public class AuthorizationTests { diff --git a/tests/HopFrame.Security.Tests/HopFrame.Security.Tests.csproj b/tests/HopFrame.Tests.Security/HopFrame.Tests.Security.csproj similarity index 100% rename from tests/HopFrame.Security.Tests/HopFrame.Security.Tests.csproj rename to tests/HopFrame.Tests.Security/HopFrame.Tests.Security.csproj diff --git a/tests/HopFrame.Web.Tests/AuthMiddlewareTests.cs b/tests/HopFrame.Tests.Web/AuthMiddlewareTests.cs similarity index 98% rename from tests/HopFrame.Web.Tests/AuthMiddlewareTests.cs rename to tests/HopFrame.Tests.Web/AuthMiddlewareTests.cs index 70d1f8e..d9e136f 100644 --- a/tests/HopFrame.Web.Tests/AuthMiddlewareTests.cs +++ b/tests/HopFrame.Tests.Web/AuthMiddlewareTests.cs @@ -2,11 +2,12 @@ using System.Security.Claims; using HopFrame.Database.Models; using HopFrame.Database.Repositories; using HopFrame.Security.Claims; +using HopFrame.Web; using HopFrame.Web.Services; using Microsoft.AspNetCore.Http; using Moq; -namespace HopFrame.Web.Tests; +namespace HopFrame.Tests.Web; public class AuthMiddlewareTests { private readonly RequestDelegate _delegate = _ => Task.CompletedTask; diff --git a/tests/HopFrame.Web.Tests/AuthServiceTests.cs b/tests/HopFrame.Tests.Web/AuthServiceTests.cs similarity index 99% rename from tests/HopFrame.Web.Tests/AuthServiceTests.cs rename to tests/HopFrame.Tests.Web/AuthServiceTests.cs index 4374148..a5df287 100644 --- a/tests/HopFrame.Web.Tests/AuthServiceTests.cs +++ b/tests/HopFrame.Tests.Web/AuthServiceTests.cs @@ -1,14 +1,14 @@ -using HopFrame.Api.Tests.Extensions; using HopFrame.Database.Models; using HopFrame.Database.Repositories; using HopFrame.Security.Claims; using HopFrame.Security.Models; +using HopFrame.Tests.Web.Extensions; using HopFrame.Web.Services; using HopFrame.Web.Services.Implementation; using Microsoft.AspNetCore.Http; using Moq; -namespace HopFrame.Web.Tests; +namespace HopFrame.Tests.Web; public class AuthServiceTests { private readonly Guid _refreshToken = Guid.NewGuid(); diff --git a/tests/HopFrame.Web.Tests/Extensions/HttpContextExtensions.cs b/tests/HopFrame.Tests.Web/Extensions/HttpContextExtensions.cs similarity index 96% rename from tests/HopFrame.Web.Tests/Extensions/HttpContextExtensions.cs rename to tests/HopFrame.Tests.Web/Extensions/HttpContextExtensions.cs index 4ccbb38..a26537a 100644 --- a/tests/HopFrame.Web.Tests/Extensions/HttpContextExtensions.cs +++ b/tests/HopFrame.Tests.Web/Extensions/HttpContextExtensions.cs @@ -1,7 +1,7 @@ using System.Web; using Microsoft.AspNetCore.Http; -namespace HopFrame.Api.Tests.Extensions; +namespace HopFrame.Tests.Web.Extensions; internal static class HttpContextExtensions { /// Extracts the partial cookie value from the header section. diff --git a/tests/HopFrame.Web.Tests/HopFrame.Web.Tests.csproj b/tests/HopFrame.Tests.Web/HopFrame.Tests.Web.csproj similarity index 100% rename from tests/HopFrame.Web.Tests/HopFrame.Web.Tests.csproj rename to tests/HopFrame.Tests.Web/HopFrame.Tests.Web.csproj From 5f746e0bc13166e9c3d24d739d82df4b7e6519f7 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Tue, 10 Dec 2024 16:55:36 +0100 Subject: [PATCH 10/12] Renamed testing projects --- HopFrame.sln | 4 ++-- .../.gitignore | 0 .../Controllers/TestController.cs | 4 ++-- .../DatabaseContext.cs | 6 +++--- .../HopFrame.Testing.Api.csproj} | 0 .../Models/Address.cs | 2 +- .../Models/Employee.cs | 2 +- .../Program.cs | 2 +- .../Properties/launchSettings.json | 0 .../appsettings.json | 0 .../.gitignore | 0 .../AdminContext.cs | 6 +++--- .../Components/App.razor | 2 +- .../Components/Layout/MainLayout.razor | 0 .../Components/Layout/MainLayout.razor.css | 0 .../Components/Layout/NavMenu.razor | 0 .../Components/Layout/NavMenu.razor.css | 0 .../Components/Pages/Counter.razor | 0 .../Components/Pages/Error.razor | 0 .../Components/Pages/Home.razor | 0 .../Components/Pages/Weather.razor | 0 .../Components/Routes.razor | 0 .../Components/_Imports.razor | 4 ++-- .../DatabaseContext.cs | 6 +++--- .../HopFrame.Testing.Web.csproj} | 0 .../Models/Address.cs | 2 +- .../Models/Employee.cs | 2 +- .../Program.cs | 4 ++-- .../Properties/launchSettings.json | 0 .../Providers/AddressProvider.cs | 4 ++-- .../Providers/EmployeeProvider.cs | 4 ++-- .../appsettings.json | 0 .../wwwroot/app.css | 0 .../wwwroot/favicon.png | Bin 34 files changed, 27 insertions(+), 27 deletions(-) rename testing/{FrontendTest => HopFrame.Testing.Api}/.gitignore (100%) rename testing/{RestApiTest => HopFrame.Testing.Api}/Controllers/TestController.cs (95%) rename testing/{RestApiTest => HopFrame.Testing.Api}/DatabaseContext.cs (80%) rename testing/{RestApiTest/RestApiTest.csproj => HopFrame.Testing.Api/HopFrame.Testing.Api.csproj} (100%) rename testing/{FrontendTest => HopFrame.Testing.Api}/Models/Address.cs (92%) rename testing/{RestApiTest => HopFrame.Testing.Api}/Models/Employee.cs (79%) rename testing/{RestApiTest => HopFrame.Testing.Api}/Program.cs (98%) rename testing/{RestApiTest => HopFrame.Testing.Api}/Properties/launchSettings.json (100%) rename testing/{FrontendTest => HopFrame.Testing.Api}/appsettings.json (100%) rename testing/{RestApiTest => HopFrame.Testing.Web}/.gitignore (100%) rename testing/{FrontendTest => HopFrame.Testing.Web}/AdminContext.cs (90%) rename testing/{FrontendTest => HopFrame.Testing.Web}/Components/App.razor (86%) rename testing/{FrontendTest => HopFrame.Testing.Web}/Components/Layout/MainLayout.razor (100%) rename testing/{FrontendTest => HopFrame.Testing.Web}/Components/Layout/MainLayout.razor.css (100%) rename testing/{FrontendTest => HopFrame.Testing.Web}/Components/Layout/NavMenu.razor (100%) rename testing/{FrontendTest => HopFrame.Testing.Web}/Components/Layout/NavMenu.razor.css (100%) rename testing/{FrontendTest => HopFrame.Testing.Web}/Components/Pages/Counter.razor (100%) rename testing/{FrontendTest => HopFrame.Testing.Web}/Components/Pages/Error.razor (100%) rename testing/{FrontendTest => HopFrame.Testing.Web}/Components/Pages/Home.razor (100%) rename testing/{FrontendTest => HopFrame.Testing.Web}/Components/Pages/Weather.razor (100%) rename testing/{FrontendTest => HopFrame.Testing.Web}/Components/Routes.razor (100%) rename testing/{FrontendTest => HopFrame.Testing.Web}/Components/_Imports.razor (83%) rename testing/{FrontendTest => HopFrame.Testing.Web}/DatabaseContext.cs (80%) rename testing/{FrontendTest/FrontendTest.csproj => HopFrame.Testing.Web/HopFrame.Testing.Web.csproj} (100%) rename testing/{RestApiTest => HopFrame.Testing.Web}/Models/Address.cs (92%) rename testing/{FrontendTest => HopFrame.Testing.Web}/Models/Employee.cs (79%) rename testing/{FrontendTest => HopFrame.Testing.Web}/Program.cs (93%) rename testing/{FrontendTest => HopFrame.Testing.Web}/Properties/launchSettings.json (100%) rename testing/{FrontendTest => HopFrame.Testing.Web}/Providers/AddressProvider.cs (91%) rename testing/{FrontendTest => HopFrame.Testing.Web}/Providers/EmployeeProvider.cs (91%) rename testing/{RestApiTest => HopFrame.Testing.Web}/appsettings.json (100%) rename testing/{FrontendTest => HopFrame.Testing.Web}/wwwroot/app.css (100%) rename testing/{FrontendTest => HopFrame.Testing.Web}/wwwroot/favicon.png (100%) diff --git a/HopFrame.sln b/HopFrame.sln index 95067c5..06d8283 100644 --- a/HopFrame.sln +++ b/HopFrame.sln @@ -2,7 +2,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Database", "src\HopFrame.Database\HopFrame.Database.csproj", "{003120AE-F38B-4632-8497-BE4505189627}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestApiTest", "testing\RestApiTest\RestApiTest.csproj", "{921159CE-AF75-44C3-A3F9-6B9B1A4E85CF}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Testing.Api", "testing\HopFrame.Testing.Api\HopFrame.Testing.Api.csproj", "{921159CE-AF75-44C3-A3F9-6B9B1A4E85CF}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Security", "src\HopFrame.Security\HopFrame.Security.csproj", "{7F82E1C6-4A42-4337-9E03-2EE6429D004F}" EndProject @@ -10,7 +10,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Api", "src\HopFram EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Web", "src\HopFrame.Web\HopFrame.Web.csproj", "{3BE585BC-13A5-4BE4-A806-E9EC2D825956}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrontendTest", "testing\FrontendTest\FrontendTest.csproj", "{8F983A37-63CF-48D5-988D-58B78EF8AECD}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Testing.Web", "testing\HopFrame.Testing.Web\HopFrame.Testing.Web.csproj", "{8F983A37-63CF-48D5-988D-58B78EF8AECD}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Web.Admin", "src\HopFrame.Web.Admin\HopFrame.Web.Admin.csproj", "{02D9F10A-664A-4EF7-BF19-310C26FF4DEB}" EndProject diff --git a/testing/FrontendTest/.gitignore b/testing/HopFrame.Testing.Api/.gitignore similarity index 100% rename from testing/FrontendTest/.gitignore rename to testing/HopFrame.Testing.Api/.gitignore diff --git a/testing/RestApiTest/Controllers/TestController.cs b/testing/HopFrame.Testing.Api/Controllers/TestController.cs similarity index 95% rename from testing/RestApiTest/Controllers/TestController.cs rename to testing/HopFrame.Testing.Api/Controllers/TestController.cs index 092784f..fb39666 100644 --- a/testing/RestApiTest/Controllers/TestController.cs +++ b/testing/HopFrame.Testing.Api/Controllers/TestController.cs @@ -2,11 +2,11 @@ using HopFrame.Api.Logic; using HopFrame.Database.Models; using HopFrame.Security.Authorization; using HopFrame.Security.Claims; +using HopFrame.Testing.Api.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -using RestApiTest.Models; -namespace RestApiTest.Controllers; +namespace HopFrame.Testing.Api.Controllers; [ApiController] [Route("test")] diff --git a/testing/RestApiTest/DatabaseContext.cs b/testing/HopFrame.Testing.Api/DatabaseContext.cs similarity index 80% rename from testing/RestApiTest/DatabaseContext.cs rename to testing/HopFrame.Testing.Api/DatabaseContext.cs index 42ae5d1..4c707f4 100644 --- a/testing/RestApiTest/DatabaseContext.cs +++ b/testing/HopFrame.Testing.Api/DatabaseContext.cs @@ -1,8 +1,8 @@ using HopFrame.Database; +using HopFrame.Testing.Api.Models; using Microsoft.EntityFrameworkCore; -using RestApiTest.Models; -namespace RestApiTest; +namespace HopFrame.Testing.Api; public class DatabaseContext : HopDbContextBase { @@ -12,7 +12,7 @@ public class DatabaseContext : HopDbContextBase { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); - optionsBuilder.UseSqlite(@"Data Source=C:\Users\leon\Documents\Projekte\HopFrame\testing\RestApiTest\bin\Debug\net8.0\test.db;Mode=ReadWrite;"); + optionsBuilder.UseSqlite(@"Data Source=C:\Users\leon\Documents\Projekte\HopFrame\testing\HopFrame.Testing.Api\bin\Debug\net8.0\test.db;Mode=ReadWrite;"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { diff --git a/testing/RestApiTest/RestApiTest.csproj b/testing/HopFrame.Testing.Api/HopFrame.Testing.Api.csproj similarity index 100% rename from testing/RestApiTest/RestApiTest.csproj rename to testing/HopFrame.Testing.Api/HopFrame.Testing.Api.csproj diff --git a/testing/FrontendTest/Models/Address.cs b/testing/HopFrame.Testing.Api/Models/Address.cs similarity index 92% rename from testing/FrontendTest/Models/Address.cs rename to testing/HopFrame.Testing.Api/Models/Address.cs index 386114d..5688ad1 100644 --- a/testing/FrontendTest/Models/Address.cs +++ b/testing/HopFrame.Testing.Api/Models/Address.cs @@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; using Microsoft.AspNetCore.Mvc.ModelBinding; -namespace RestApiTest.Models; +namespace HopFrame.Testing.Api.Models; public class Address { [ForeignKey("Employee")] diff --git a/testing/RestApiTest/Models/Employee.cs b/testing/HopFrame.Testing.Api/Models/Employee.cs similarity index 79% rename from testing/RestApiTest/Models/Employee.cs rename to testing/HopFrame.Testing.Api/Models/Employee.cs index 6f70edc..f7e3e27 100644 --- a/testing/RestApiTest/Models/Employee.cs +++ b/testing/HopFrame.Testing.Api/Models/Employee.cs @@ -1,4 +1,4 @@ -namespace RestApiTest.Models; +namespace HopFrame.Testing.Api.Models; public class Employee { public int EmployeeId { get; set; } diff --git a/testing/RestApiTest/Program.cs b/testing/HopFrame.Testing.Api/Program.cs similarity index 98% rename from testing/RestApiTest/Program.cs rename to testing/HopFrame.Testing.Api/Program.cs index bfcbc38..6651ecd 100644 --- a/testing/RestApiTest/Program.cs +++ b/testing/HopFrame.Testing.Api/Program.cs @@ -1,4 +1,4 @@ -using RestApiTest; +using HopFrame.Testing.Api; using HopFrame.Api.Extensions; using Microsoft.OpenApi.Models; diff --git a/testing/RestApiTest/Properties/launchSettings.json b/testing/HopFrame.Testing.Api/Properties/launchSettings.json similarity index 100% rename from testing/RestApiTest/Properties/launchSettings.json rename to testing/HopFrame.Testing.Api/Properties/launchSettings.json diff --git a/testing/FrontendTest/appsettings.json b/testing/HopFrame.Testing.Api/appsettings.json similarity index 100% rename from testing/FrontendTest/appsettings.json rename to testing/HopFrame.Testing.Api/appsettings.json diff --git a/testing/RestApiTest/.gitignore b/testing/HopFrame.Testing.Web/.gitignore similarity index 100% rename from testing/RestApiTest/.gitignore rename to testing/HopFrame.Testing.Web/.gitignore diff --git a/testing/FrontendTest/AdminContext.cs b/testing/HopFrame.Testing.Web/AdminContext.cs similarity index 90% rename from testing/FrontendTest/AdminContext.cs rename to testing/HopFrame.Testing.Web/AdminContext.cs index 2eaff8d..9d33628 100644 --- a/testing/FrontendTest/AdminContext.cs +++ b/testing/HopFrame.Testing.Web/AdminContext.cs @@ -1,10 +1,10 @@ -using FrontendTest.Providers; using HopFrame.Web.Admin; using HopFrame.Web.Admin.Generators; using HopFrame.Web.Admin.Models; -using RestApiTest.Models; +using HopFrame.Testing.Api.Models; +using HopFrame.Testing.Web.Providers; -namespace FrontendTest; +namespace HopFrame.Testing.Web; public class AdminContext : AdminPagesContext { diff --git a/testing/FrontendTest/Components/App.razor b/testing/HopFrame.Testing.Web/Components/App.razor similarity index 86% rename from testing/FrontendTest/Components/App.razor rename to testing/HopFrame.Testing.Web/Components/App.razor index 35c8065..b3faba0 100644 --- a/testing/FrontendTest/Components/App.razor +++ b/testing/HopFrame.Testing.Web/Components/App.razor @@ -7,7 +7,7 @@ - + diff --git a/testing/FrontendTest/Components/Layout/MainLayout.razor b/testing/HopFrame.Testing.Web/Components/Layout/MainLayout.razor similarity index 100% rename from testing/FrontendTest/Components/Layout/MainLayout.razor rename to testing/HopFrame.Testing.Web/Components/Layout/MainLayout.razor diff --git a/testing/FrontendTest/Components/Layout/MainLayout.razor.css b/testing/HopFrame.Testing.Web/Components/Layout/MainLayout.razor.css similarity index 100% rename from testing/FrontendTest/Components/Layout/MainLayout.razor.css rename to testing/HopFrame.Testing.Web/Components/Layout/MainLayout.razor.css diff --git a/testing/FrontendTest/Components/Layout/NavMenu.razor b/testing/HopFrame.Testing.Web/Components/Layout/NavMenu.razor similarity index 100% rename from testing/FrontendTest/Components/Layout/NavMenu.razor rename to testing/HopFrame.Testing.Web/Components/Layout/NavMenu.razor diff --git a/testing/FrontendTest/Components/Layout/NavMenu.razor.css b/testing/HopFrame.Testing.Web/Components/Layout/NavMenu.razor.css similarity index 100% rename from testing/FrontendTest/Components/Layout/NavMenu.razor.css rename to testing/HopFrame.Testing.Web/Components/Layout/NavMenu.razor.css diff --git a/testing/FrontendTest/Components/Pages/Counter.razor b/testing/HopFrame.Testing.Web/Components/Pages/Counter.razor similarity index 100% rename from testing/FrontendTest/Components/Pages/Counter.razor rename to testing/HopFrame.Testing.Web/Components/Pages/Counter.razor diff --git a/testing/FrontendTest/Components/Pages/Error.razor b/testing/HopFrame.Testing.Web/Components/Pages/Error.razor similarity index 100% rename from testing/FrontendTest/Components/Pages/Error.razor rename to testing/HopFrame.Testing.Web/Components/Pages/Error.razor diff --git a/testing/FrontendTest/Components/Pages/Home.razor b/testing/HopFrame.Testing.Web/Components/Pages/Home.razor similarity index 100% rename from testing/FrontendTest/Components/Pages/Home.razor rename to testing/HopFrame.Testing.Web/Components/Pages/Home.razor diff --git a/testing/FrontendTest/Components/Pages/Weather.razor b/testing/HopFrame.Testing.Web/Components/Pages/Weather.razor similarity index 100% rename from testing/FrontendTest/Components/Pages/Weather.razor rename to testing/HopFrame.Testing.Web/Components/Pages/Weather.razor diff --git a/testing/FrontendTest/Components/Routes.razor b/testing/HopFrame.Testing.Web/Components/Routes.razor similarity index 100% rename from testing/FrontendTest/Components/Routes.razor rename to testing/HopFrame.Testing.Web/Components/Routes.razor diff --git a/testing/FrontendTest/Components/_Imports.razor b/testing/HopFrame.Testing.Web/Components/_Imports.razor similarity index 83% rename from testing/FrontendTest/Components/_Imports.razor rename to testing/HopFrame.Testing.Web/Components/_Imports.razor index b17e0c0..f7abb33 100644 --- a/testing/FrontendTest/Components/_Imports.razor +++ b/testing/HopFrame.Testing.Web/Components/_Imports.razor @@ -6,5 +6,5 @@ @using static Microsoft.AspNetCore.Components.Web.RenderMode @using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.JSInterop -@using FrontendTest -@using FrontendTest.Components \ No newline at end of file +@using HopFrame.Testing.Web +@using HopFrame.Testing.Web.Components \ No newline at end of file diff --git a/testing/FrontendTest/DatabaseContext.cs b/testing/HopFrame.Testing.Web/DatabaseContext.cs similarity index 80% rename from testing/FrontendTest/DatabaseContext.cs rename to testing/HopFrame.Testing.Web/DatabaseContext.cs index c1d3c7b..bd12346 100644 --- a/testing/FrontendTest/DatabaseContext.cs +++ b/testing/HopFrame.Testing.Web/DatabaseContext.cs @@ -1,8 +1,8 @@ using HopFrame.Database; using Microsoft.EntityFrameworkCore; -using RestApiTest.Models; +using HopFrame.Testing.Api.Models; -namespace FrontendTest; +namespace HopFrame.Testing.Web; public class DatabaseContext : HopDbContextBase { public DbSet Employees { get; set; } @@ -11,7 +11,7 @@ public class DatabaseContext : HopDbContextBase { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); - optionsBuilder.UseSqlite(@"Data Source=C:\Users\leon\Documents\Projekte\HopFrame\testing\RestApiTest\bin\Debug\net8.0\test.db;Mode=ReadWrite;"); + optionsBuilder.UseSqlite(@"Data Source=C:\Users\leon\Documents\Projekte\HopFrame\testing\HopFrame.Testing.Api\bin\Debug\net8.0\test.db;Mode=ReadWrite;"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { diff --git a/testing/FrontendTest/FrontendTest.csproj b/testing/HopFrame.Testing.Web/HopFrame.Testing.Web.csproj similarity index 100% rename from testing/FrontendTest/FrontendTest.csproj rename to testing/HopFrame.Testing.Web/HopFrame.Testing.Web.csproj diff --git a/testing/RestApiTest/Models/Address.cs b/testing/HopFrame.Testing.Web/Models/Address.cs similarity index 92% rename from testing/RestApiTest/Models/Address.cs rename to testing/HopFrame.Testing.Web/Models/Address.cs index 386114d..5688ad1 100644 --- a/testing/RestApiTest/Models/Address.cs +++ b/testing/HopFrame.Testing.Web/Models/Address.cs @@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; using Microsoft.AspNetCore.Mvc.ModelBinding; -namespace RestApiTest.Models; +namespace HopFrame.Testing.Api.Models; public class Address { [ForeignKey("Employee")] diff --git a/testing/FrontendTest/Models/Employee.cs b/testing/HopFrame.Testing.Web/Models/Employee.cs similarity index 79% rename from testing/FrontendTest/Models/Employee.cs rename to testing/HopFrame.Testing.Web/Models/Employee.cs index 6f70edc..f7e3e27 100644 --- a/testing/FrontendTest/Models/Employee.cs +++ b/testing/HopFrame.Testing.Web/Models/Employee.cs @@ -1,4 +1,4 @@ -namespace RestApiTest.Models; +namespace HopFrame.Testing.Api.Models; public class Employee { public int EmployeeId { get; set; } diff --git a/testing/FrontendTest/Program.cs b/testing/HopFrame.Testing.Web/Program.cs similarity index 93% rename from testing/FrontendTest/Program.cs rename to testing/HopFrame.Testing.Web/Program.cs index 7547722..7957fff 100644 --- a/testing/FrontendTest/Program.cs +++ b/testing/HopFrame.Testing.Web/Program.cs @@ -1,5 +1,5 @@ -using FrontendTest; -using FrontendTest.Components; +using HopFrame.Testing.Web; +using HopFrame.Testing.Web.Components; using HopFrame.Web; using HopFrame.Web.Admin; diff --git a/testing/FrontendTest/Properties/launchSettings.json b/testing/HopFrame.Testing.Web/Properties/launchSettings.json similarity index 100% rename from testing/FrontendTest/Properties/launchSettings.json rename to testing/HopFrame.Testing.Web/Properties/launchSettings.json diff --git a/testing/FrontendTest/Providers/AddressProvider.cs b/testing/HopFrame.Testing.Web/Providers/AddressProvider.cs similarity index 91% rename from testing/FrontendTest/Providers/AddressProvider.cs rename to testing/HopFrame.Testing.Web/Providers/AddressProvider.cs index de5f13f..6ed1d6f 100644 --- a/testing/FrontendTest/Providers/AddressProvider.cs +++ b/testing/HopFrame.Testing.Web/Providers/AddressProvider.cs @@ -1,8 +1,8 @@ using HopFrame.Web.Admin; using Microsoft.EntityFrameworkCore; -using RestApiTest.Models; +using HopFrame.Testing.Api.Models; -namespace FrontendTest.Providers; +namespace HopFrame.Testing.Web.Providers; public class AddressProvider(DatabaseContext context) : ModelProvider
{ diff --git a/testing/FrontendTest/Providers/EmployeeProvider.cs b/testing/HopFrame.Testing.Web/Providers/EmployeeProvider.cs similarity index 91% rename from testing/FrontendTest/Providers/EmployeeProvider.cs rename to testing/HopFrame.Testing.Web/Providers/EmployeeProvider.cs index 89f7b84..9078eea 100644 --- a/testing/FrontendTest/Providers/EmployeeProvider.cs +++ b/testing/HopFrame.Testing.Web/Providers/EmployeeProvider.cs @@ -1,8 +1,8 @@ using HopFrame.Web.Admin; using Microsoft.EntityFrameworkCore; -using RestApiTest.Models; +using HopFrame.Testing.Api.Models; -namespace FrontendTest.Providers; +namespace HopFrame.Testing.Web.Providers; public class EmployeeProvider(DatabaseContext context) : ModelProvider { diff --git a/testing/RestApiTest/appsettings.json b/testing/HopFrame.Testing.Web/appsettings.json similarity index 100% rename from testing/RestApiTest/appsettings.json rename to testing/HopFrame.Testing.Web/appsettings.json diff --git a/testing/FrontendTest/wwwroot/app.css b/testing/HopFrame.Testing.Web/wwwroot/app.css similarity index 100% rename from testing/FrontendTest/wwwroot/app.css rename to testing/HopFrame.Testing.Web/wwwroot/app.css diff --git a/testing/FrontendTest/wwwroot/favicon.png b/testing/HopFrame.Testing.Web/wwwroot/favicon.png similarity index 100% rename from testing/FrontendTest/wwwroot/favicon.png rename to testing/HopFrame.Testing.Web/wwwroot/favicon.png From 7c835ea49b2d9532a22c5344406b8b623d1f8e07 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Wed, 11 Dec 2024 21:29:03 +0100 Subject: [PATCH 11/12] Started working on UnitTests for frontend --- HopFrame.sln.DotSettings.user | 17 +++ .../Pages/Administration/AdminLogin.razor | 2 +- .../HopFrame.Tests.Web.csproj | 1 + .../Pages/AdminLoginTests.cs | 123 ++++++++++++++++ .../Pages/AuthorizedViewTests.cs | 133 ++++++++++++++++++ 5 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 tests/HopFrame.Tests.Web/Pages/AdminLoginTests.cs create mode 100644 tests/HopFrame.Tests.Web/Pages/AuthorizedViewTests.cs diff --git a/HopFrame.sln.DotSettings.user b/HopFrame.sln.DotSettings.user index ea6ada4..1e30ef8 100644 --- a/HopFrame.sln.DotSettings.user +++ b/HopFrame.sln.DotSettings.user @@ -36,6 +36,23 @@ + + + + + + + + + + + + + + + + + diff --git a/src/HopFrame.Web/Pages/Administration/AdminLogin.razor b/src/HopFrame.Web/Pages/Administration/AdminLogin.razor index 1d9a61e..8e0f1e1 100644 --- a/src/HopFrame.Web/Pages/Administration/AdminLogin.razor +++ b/src/HopFrame.Web/Pages/Administration/AdminLogin.razor @@ -43,7 +43,7 @@ private UserLogin UserLogin { get; set; } [SupplyParameterFromQuery(Name = "redirect")] - private string RedirectAfter { get; set; } + public string RedirectAfter { get; set; } private const string DefaultRedirect = "/administration"; diff --git a/tests/HopFrame.Tests.Web/HopFrame.Tests.Web.csproj b/tests/HopFrame.Tests.Web/HopFrame.Tests.Web.csproj index 062ade0..b6e4aa4 100644 --- a/tests/HopFrame.Tests.Web/HopFrame.Tests.Web.csproj +++ b/tests/HopFrame.Tests.Web/HopFrame.Tests.Web.csproj @@ -10,6 +10,7 @@ + diff --git a/tests/HopFrame.Tests.Web/Pages/AdminLoginTests.cs b/tests/HopFrame.Tests.Web/Pages/AdminLoginTests.cs new file mode 100644 index 0000000..0e2909a --- /dev/null +++ b/tests/HopFrame.Tests.Web/Pages/AdminLoginTests.cs @@ -0,0 +1,123 @@ +using BlazorStrap; +using Bunit; +using Bunit.TestDoubles; +using CurrieTechnologies.Razor.SweetAlert2; +using HopFrame.Security.Models; +using HopFrame.Web.Pages.Administration; +using HopFrame.Web.Services; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.DependencyInjection; +using Moq; + +namespace HopFrame.Tests.Web.Pages; + +public class AdminLoginTests : TestContext { + + private (IRenderedComponent, NavigationManager) SetupEnvironment(bool correctCredentials = true) { + var auth = new Mock(); + auth + .Setup(a => a.Login(It.IsAny())) + .ReturnsAsync(correctCredentials); + + Services.AddSweetAlert2(); + Services.AddBlazorStrap(); + Services.AddSingleton(auth.Object); + var navigator = Services.GetRequiredService(); + + var component = RenderComponent(); + return (component, navigator); + } + + [Fact] + public void Login_Has_RequiredFields() { + // Arrange + var (component, _) = SetupEnvironment(); + + // Act + var inputs = component.FindAll("input"); + var buttons = component.FindAll("button"); + var form = component.FindAll("form"); + + // Assert + Assert.Equal(2, inputs.Count); + Assert.Single(buttons); + Assert.Single(form); + Assert.Equal("submit", buttons[0].Attributes.GetNamedItem("type")?.Value); + + foreach (var input in inputs) { + var attribute = input.Attributes.GetNamedItem("required"); + Assert.NotNull(attribute); + Assert.NotEqual("false", attribute?.Value); + } + } + + [Fact] + public void Login_With_CorrectCredentials_Should_Redirect() { + // Arrange + var (component, nav) = SetupEnvironment(); + var email = component.Find("""input[type="email"]"""); + var password = component.Find("""input[type="password"]"""); + var submit = component.Find("button"); + + // Act + email.Change("test@example.com"); + password.Change("1234567890"); + submit.Click(); + + // Assert + Assert.EndsWith("/administration", nav.Uri); + } + + [Fact] + public void Login_With_CorrectCredentials_And_CustomRedirect_Should_Redirect() { + // Arrange + var (component, nav) = SetupEnvironment(); + var email = component.Find("""input[type="email"]"""); + var password = component.Find("""input[type="password"]"""); + var submit = component.Find("button"); + + component.Instance.RedirectAfter = "testRedirect"; + + // Act + email.Change("test@example.com"); + password.Change("1234567890"); + submit.Click(); + + // Assert + Assert.EndsWith("/administration/testRedirect", nav.Uri); + } + + [Fact] + public void Login_With_IncorrectCredentials_Should_Fail() { + // Arrange + var (component, nav) = SetupEnvironment(false); + var email = component.Find("""input[type="email"]"""); + var password = component.Find("""input[type="password"]"""); + var submit = component.Find("button"); + + // Act + email.Change("test@example.com"); + password.Change("1234567890"); + submit.Click(); + + // Assert + Assert.False(nav.Uri.EndsWith("/administration")); + } + + [Fact] + public void Login_With_IncorrectCredentials_DisplaysError() { + // Arrange + var (component, _) = SetupEnvironment(false); + var email = component.Find("""input[type="email"]"""); + var password = component.Find("""input[type="password"]"""); + var submit = component.Find("button"); + + // Act + email.Change("test@example.com"); + password.Change("1234567890"); + submit.Click(); + + // Assert + Assert.Contains("Email or password does not match any account!", component.Markup); + } +} \ No newline at end of file diff --git a/tests/HopFrame.Tests.Web/Pages/AuthorizedViewTests.cs b/tests/HopFrame.Tests.Web/Pages/AuthorizedViewTests.cs new file mode 100644 index 0000000..92b9611 --- /dev/null +++ b/tests/HopFrame.Tests.Web/Pages/AuthorizedViewTests.cs @@ -0,0 +1,133 @@ +using System.Security.Claims; +using Bunit; +using Bunit.TestDoubles; +using HopFrame.Security.Authentication; +using HopFrame.Security.Claims; +using HopFrame.Web.Components; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Moq; + +namespace HopFrame.Tests.Web.Pages; + +public class AuthorizedViewTests : TestContext { + private readonly string _testRedirect = "testRedirect"; + private readonly string _testPermission = "test.permission"; + private readonly string _innerHtml = "

Inner Render

"; + + public NavigationManager SetupEnvironment(bool authenticated = true, params string[] userPermissions) { + var auth = new Mock(); + auth + .Setup(a => a.IsAuthenticated) + .Returns(authenticated); + + var context = new DefaultHttpContext(); + var claims = userPermissions?.Select(perm => new Claim(HopFrameClaimTypes.Permission, perm)).ToList(); + context.User.AddIdentity(new ClaimsIdentity(claims, HopFrameAuthentication.SchemeName)); + var accessor = new Mock(); + accessor + .Setup(a => a.HttpContext) + .Returns(context); + + Services.AddSingleton(auth.Object); + Services.AddSingleton(accessor.Object); + return Services.GetRequiredService(); + } + + [Fact] + public void AuthorizedView_With_NoValidLogin_And_Redirection_Should_Redirect() { + // Arrange + var navigator = SetupEnvironment(false); + + // Act + RenderComponent(parameters => parameters + .Add(a => a.RedirectIfUnauthorized, _testRedirect)); + + // Assert + Assert.EndsWith(_testRedirect, navigator.Uri); + } + + [Fact] + public void AuthorizedView_With_NoPermissions_And_Redirection_Should_Redirect() { + // Arrange + var navigator = SetupEnvironment(); + + // Act + RenderComponent(parameters => parameters + .Add(a => a.RedirectIfUnauthorized, _testRedirect) + .Add(a => a.Permission, _testPermission)); + + // Assert + Assert.EndsWith(_testRedirect, navigator.Uri); + } + + [Fact] + public void AuthorizedView_With_FewPermissions_And_Redirection_Should_Redirect() { + // Arrange + var navigator = SetupEnvironment(true, "other.permission"); + + // Act + RenderComponent(parameters => parameters + .Add(a => a.RedirectIfUnauthorized, _testRedirect) + .Add(a => a.Permissions, [_testPermission, "other.permission"])); + + // Assert + Assert.EndsWith(_testRedirect, navigator.Uri); + } + + [Fact] + public void AuthorizedView_With_Permissions_And_Redirection_Should_NotRedirect() { + // Arrange + var navigator = SetupEnvironment(true, _testPermission); + + // Act + RenderComponent(parameters => parameters + .Add(a => a.RedirectIfUnauthorized, _testRedirect) + .Add(a => a.Permission, _testPermission)); + + // Assert + Assert.False(navigator.Uri.EndsWith(_testRedirect)); + } + + [Fact] + public void AuthorizedView_With_AllPermissions_And_Redirection_Should_NotRedirect() { + // Arrange + var navigator = SetupEnvironment(true, _testPermission, "other.permission"); + + // Act + RenderComponent(parameters => parameters + .Add(a => a.RedirectIfUnauthorized, _testRedirect) + .Add(a => a.Permissions, [_testPermission, "other.permission"])); + + // Assert + Assert.False(navigator.Uri.EndsWith(_testRedirect)); + } + + [Fact] + public void AuthorizedView_With_ChildComponent_And_ValidLogin_Should_DisplayChildren() { + // Arrange + SetupEnvironment(); + + // Act + var component = RenderComponent(parameters => parameters + .AddChildContent(_innerHtml)); + + // Assert + Assert.Contains(_innerHtml, component.Markup); + } + + [Fact] + public void AuthorizedView_With_ChildComponent_And_InvalidLogin_Should_NotDisplayChildren() { + // Arrange + SetupEnvironment(false); + + // Act + var component = RenderComponent(parameters => parameters + .AddChildContent(_innerHtml)); + + // Assert + Assert.DoesNotContain(_innerHtml, component.Markup); + } + +} \ No newline at end of file From c4ee8bb1e0f3173d234870b5c6c686f26a0a6341 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Sat, 21 Dec 2024 13:10:46 +0100 Subject: [PATCH 12/12] Fixed mistake corrected in v2.0.1 --- src/HopFrame.Web/Pages/Administration/AdminPageList.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor index 163f66f..bf4c17d 100644 --- a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor +++ b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor @@ -33,7 +33,7 @@ - + Add Entry