Feature/testing #39
@@ -21,7 +21,7 @@ build:
|
||||
test:
|
||||
stage: test
|
||||
script:
|
||||
- dotnet test --no-restore --verbosity normal
|
||||
- dotnet test --verbosity normal
|
||||
|
||||
publish:
|
||||
stage: publish
|
||||
|
||||
45
HopFrame.sln
45
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}") = "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,10 +10,24 @@ 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}") = "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
|
||||
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
|
||||
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.Tests.Security", "tests\HopFrame.Tests.Security\HopFrame.Tests.Security.csproj", "{6747753A-6059-48F1-B779-D73765A373A6}"
|
||||
EndProject
|
||||
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.Tests.Web", "tests\HopFrame.Tests.Web\HopFrame.Tests.Web.csproj", "{566C13B9-4ECA-48C4-8D02-FEB6CDF523E6}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -48,7 +62,34 @@ 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
|
||||
{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
|
||||
{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
|
||||
{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}
|
||||
{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}
|
||||
{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
|
||||
|
||||
@@ -1,8 +1,74 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationSchemeOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F8525b7a9e58c77f532f1a88d4f2897e3c2baf316b9eb2c391b242a3885fcce6_003FAuthenticationSchemeOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEditContextDataAnnotationsExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fbc307cd57fb42fc4c7fb9795381958122734d3750f41b6c1735c7d132ecda70_003FEditContextDataAnnotationsExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fbd1d5c50194fea68ff3559c160230b0ab50f5acf4ce3061bffd6d62958e2182_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIHttpContextAccessor_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffe1b239a13ce466b829e79538c654ff54e938_003Fa5_003F4093f165_003FIHttpContextAccessor_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AList_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fb7208b3f72528d22781d25fde9a55271bdf2b5aade4f03b1324579a25493cd8_003FList_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F2c8e7ca976f350cba9836d5565dac56b11e0b56656fa786460eb1395857a6fa_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AValidationMessageStore_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Ffc81648e473bb3cc818f71427c286ecddc3604d2f4c69c565205bb89e8b4ef4_003FValidationMessageStore_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/Environment/AssemblyExplorer/XmlDocument/@EntryValue"><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></s:String></wpf:ResourceDictionary>
|
||||
</AssemblyExplorer></s:String>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</wpf:ResourceDictionary>
|
||||
@@ -22,4 +22,10 @@
|
||||
<None Include="README.md" Pack="true" PackagePath="\"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>HopFrame.Tests.Api</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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<LogicResult<SingleValueResult<string>>> 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<LogicResult<SingleValueResult<string>>> Register(UserRegister register) {
|
||||
if (register.Password.Length < 8)
|
||||
return LogicResult<SingleValueResult<string>>.Conflict("Password needs to be at least 8 characters long");
|
||||
return LogicResult<SingleValueResult<string>>.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<SingleValueResult<string>>.Conflict("Refresh token not provided");
|
||||
return LogicResult<SingleValueResult<string>>.BadRequest("Refresh token not provided");
|
||||
|
||||
var token = await tokens.GetToken(refreshToken);
|
||||
|
||||
if (token.Type != Token.RefreshTokenType)
|
||||
return LogicResult<SingleValueResult<string>>.BadRequest("The provided token is not a refresh token");
|
||||
|
||||
if (token is null)
|
||||
return LogicResult<SingleValueResult<string>>.NotFound("Refresh token not valid");
|
||||
|
||||
if (token.Type != Token.RefreshTokenType)
|
||||
return LogicResult<SingleValueResult<string>>.Conflict("The provided token is not a refresh token");
|
||||
|
||||
if (token.CreatedAt + HopFrameAuthentication.RefreshTokenTime < DateTime.Now)
|
||||
return LogicResult<SingleValueResult<string>>.Conflict("Refresh token is expired");
|
||||
return LogicResult<SingleValueResult<string>>.Forbidden("Refresh token is expired");
|
||||
|
||||
var accessToken = await tokens.CreateToken(Token.AccessTokenType, token.Owner);
|
||||
|
||||
|
||||
@@ -22,4 +22,10 @@
|
||||
<None Include="README.md" Pack="true" PackagePath="\"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>HopFrame.Tests.Database</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -17,7 +17,6 @@ public class HopFrameAuthentication(
|
||||
UrlEncoder encoder,
|
||||
ISystemClock clock,
|
||||
ITokenRepository tokens,
|
||||
IUserRepository users,
|
||||
IPermissionRepository perms)
|
||||
: AuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder, clock) {
|
||||
|
||||
|
||||
@@ -35,4 +35,10 @@
|
||||
<None Include="README.md" Pack="true" PackagePath="\"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>HopFrame.Tests.Web</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<div class="d-flex" role="search" id="search">
|
||||
<input class="form-control me-2 input-dark" type="search" placeholder="Search" aria-label="Search" @oninput="TriggerSearch">
|
||||
</div>
|
||||
<AuthorizedView Permission="@Security.AdminPermissions.AddGroup">
|
||||
<AuthorizedView Permission="@_pageData.Permissions.Create">
|
||||
<BSButton IsSubmit="false" Color="BSColor.Success" @onclick="Create">Add Entry</BSButton>
|
||||
</AuthorizedView>
|
||||
</div>
|
||||
|
||||
@@ -91,15 +91,12 @@ internal class AuthService(
|
||||
}
|
||||
|
||||
public async Task<bool> 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;
|
||||
}
|
||||
|
||||
@@ -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")]
|
||||
@@ -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\test\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) {
|
||||
@@ -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")]
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace RestApiTest.Models;
|
||||
namespace HopFrame.Testing.Api.Models;
|
||||
|
||||
public class Employee {
|
||||
public int EmployeeId { get; set; }
|
||||
@@ -1,4 +1,4 @@
|
||||
using RestApiTest;
|
||||
using HopFrame.Testing.Api;
|
||||
using HopFrame.Api.Extensions;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<base href="/"/>
|
||||
<link rel="stylesheet" href="bootstrap/bootstrap.min.css"/>
|
||||
<link rel="stylesheet" href="app.css"/>
|
||||
<link rel="stylesheet" href="FrontendTest.styles.css"/>
|
||||
<link rel="stylesheet" href="HopFrame.Testing.Web.styles.css"/>
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
<HeadOutlet/>
|
||||
</head>
|
||||
@@ -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
|
||||
@using HopFrame.Testing.Web
|
||||
@using HopFrame.Testing.Web.Components
|
||||
@@ -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<Employee> 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\test\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) {
|
||||
@@ -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")]
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace RestApiTest.Models;
|
||||
namespace HopFrame.Testing.Api.Models;
|
||||
|
||||
public class Employee {
|
||||
public int EmployeeId { get; set; }
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Address> {
|
||||
|
||||
@@ -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<Employee> {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
403
tests/HopFrame.Tests.Api/AuthLogicTests.cs
Normal file
403
tests/HopFrame.Tests.Api/AuthLogicTests.cs
Normal file
@@ -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.Tests.Api.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.Tests.Api;
|
||||
|
||||
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<IRequestCookieCollection>();
|
||||
cookies
|
||||
.SetupGet(c => c[ITokenContext.RefreshTokenType])
|
||||
.Returns(providedTokenCookie);
|
||||
accessor.HttpContext.Request.Cookies = cookies.Object;
|
||||
}
|
||||
|
||||
if (provideAccessToken) {
|
||||
var claims = new List<Claim> {
|
||||
new(HopFrameClaimTypes.AccessTokenId, _accessToken.ToString())
|
||||
};
|
||||
accessor.HttpContext.User.AddIdentity(new ClaimsIdentity(claims, HopFrameAuthentication.SchemeName));
|
||||
}
|
||||
|
||||
var users = new Mock<IUserRepository>();
|
||||
users
|
||||
.Setup(u => u.GetUserByEmail(It.Is<string>(email => CreateDummyUser().Email == email)))
|
||||
.ReturnsAsync(CreateDummyUser());
|
||||
users
|
||||
.Setup(u => u.CheckUserPassword(It.Is<User>(u => u.Email == CreateDummyUser().Email), It.IsAny<string>()))
|
||||
.ReturnsAsync(passwordIsCorrect);
|
||||
users
|
||||
.Setup(u => u.AddUser(It.IsAny<User>()))
|
||||
.ReturnsAsync(CreateDummyUser());
|
||||
users
|
||||
.Setup(u => u.GetUsers())
|
||||
.ReturnsAsync(new List<User> { CreateDummyUser() });
|
||||
|
||||
var tokens = new Mock<ITokenRepository>();
|
||||
tokens
|
||||
.Setup(t => t.CreateToken(It.Is<int>(t => t == Token.RefreshTokenType), It.IsAny<User>()))
|
||||
.ReturnsAsync(new Token {
|
||||
Content = _refreshToken,
|
||||
Type = Token.RefreshTokenType
|
||||
});
|
||||
tokens
|
||||
.Setup(t => t.CreateToken(It.Is<int>(t => t == Token.AccessTokenType), It.IsAny<User>()))
|
||||
.ReturnsAsync(new Token {
|
||||
Content = _accessToken,
|
||||
Type = Token.AccessTokenType
|
||||
});
|
||||
tokens
|
||||
.Setup(t => t.GetToken(It.Is<string>(token => token == _refreshToken.ToString())))
|
||||
.ReturnsAsync(providedRefreshToken);
|
||||
|
||||
var context = new Mock<ITokenContext>();
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
29
tests/HopFrame.Tests.Api/Extensions/HttpContextExtensions.cs
Normal file
29
tests/HopFrame.Tests.Api/Extensions/HttpContextExtensions.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System.Web;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace HopFrame.Tests.Api.Extensions;
|
||||
|
||||
internal static class HttpContextExtensions {
|
||||
/// <summary>Extracts the partial cookie value from the header section.</summary>
|
||||
/// <param name="headers"><inheritdoc cref="IHeaderDictionary" path="/summary"/></param>
|
||||
/// <param name="key">The key for identifying the cookie.</param>
|
||||
/// <returns>The value of the cookie.</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
28
tests/HopFrame.Tests.Api/HopFrame.Tests.Api.csproj
Normal file
28
tests/HopFrame.Tests.Api/HopFrame.Tests.Api.csproj
Normal file
@@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>disable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
|
||||
<PackageReference Include="Moq" Version="4.20.72" />
|
||||
<PackageReference Include="xunit" Version="2.5.3"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\HopFrame.Api\HopFrame.Api.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
12
tests/HopFrame.Tests.Database/Data/DatabaseContext.cs
Normal file
12
tests/HopFrame.Tests.Database/Data/DatabaseContext.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using HopFrame.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace HopFrame.Tests.Database.Data;
|
||||
|
||||
public class DatabaseContext : HopDbContextBase {
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
|
||||
base.OnConfiguring(optionsBuilder);
|
||||
|
||||
optionsBuilder.UseInMemoryDatabase(Guid.NewGuid().ToString());
|
||||
}
|
||||
}
|
||||
28
tests/HopFrame.Tests.Database/HopFrame.Tests.Database.csproj
Normal file
28
tests/HopFrame.Tests.Database/HopFrame.Tests.Database.csproj
Normal file
@@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
|
||||
<PackageReference Include="xunit" Version="2.5.3"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\HopFrame.Database\HopFrame.Database.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
59
tests/HopFrame.Tests.Database/PermissionValidatorTests.cs
Normal file
59
tests/HopFrame.Tests.Database/PermissionValidatorTests.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using HopFrame.Database;
|
||||
|
||||
namespace HopFrame.Tests.Database;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
using HopFrame.Database.Models;
|
||||
using HopFrame.Database.Repositories;
|
||||
using HopFrame.Database.Repositories.Implementation;
|
||||
using HopFrame.Tests.Database.Data;
|
||||
|
||||
namespace HopFrame.Tests.Database.Repositories;
|
||||
|
||||
public class GroupRepositoryTests {
|
||||
|
||||
private async Task<(DatabaseContext, IGroupRepository)> SetupEnvironment(int count = 5) {
|
||||
var context = new DatabaseContext();
|
||||
var repo = new GroupRepository<DatabaseContext>(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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
using HopFrame.Database.Models;
|
||||
using HopFrame.Database.Repositories;
|
||||
using HopFrame.Database.Repositories.Implementation;
|
||||
using HopFrame.Tests.Database.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace HopFrame.Tests.Database.Repositories;
|
||||
|
||||
public class PermissionRepositoryTests {
|
||||
|
||||
private async Task<(DatabaseContext, IPermissionRepository)> SetupEnvironment(int count = 5) {
|
||||
var context = new DatabaseContext();
|
||||
var repo = new PermissionRepository<DatabaseContext>(context, new GroupRepository<DatabaseContext>(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<Permission> {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using HopFrame.Database.Models;
|
||||
using HopFrame.Database.Repositories;
|
||||
using HopFrame.Database.Repositories.Implementation;
|
||||
using HopFrame.Tests.Database.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace HopFrame.Tests.Database.Repositories;
|
||||
|
||||
public class TokenRepositoryTests {
|
||||
|
||||
private async Task<(DatabaseContext, ITokenRepository)> SetupEnvironment(int count = 5) {
|
||||
var context = new DatabaseContext();
|
||||
var repo = new TokenRepository<DatabaseContext>(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<Token> {
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
using HopFrame.Database.Models;
|
||||
using HopFrame.Database.Repositories;
|
||||
using HopFrame.Database.Repositories.Implementation;
|
||||
using HopFrame.Tests.Database.Data;
|
||||
|
||||
namespace HopFrame.Tests.Database.Repositories;
|
||||
|
||||
public class UserRepositoryTests {
|
||||
|
||||
private async Task<(DatabaseContext, IUserRepository)> SetupEnvironment(int count = 5) {
|
||||
var context = new DatabaseContext();
|
||||
var repo = new UserRepository<DatabaseContext>(context, new GroupRepository<DatabaseContext>(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);
|
||||
}
|
||||
|
||||
}
|
||||
141
tests/HopFrame.Tests.Security/AuthenticationTests.cs
Normal file
141
tests/HopFrame.Tests.Security/AuthenticationTests.cs
Normal file
@@ -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.Tests.Security;
|
||||
|
||||
public class AuthenticationTests {
|
||||
|
||||
private async Task<HopFrameAuthentication> SetupEnvironment(Token correctToken = null, string providedToken = null) {
|
||||
var options = new Mock<IOptionsMonitor<AuthenticationSchemeOptions>>();
|
||||
options
|
||||
.Setup(x => x.Get(It.IsAny<string>()))
|
||||
.Returns(new AuthenticationSchemeOptions());
|
||||
|
||||
var logger = new Mock<ILoggerFactory>();
|
||||
logger
|
||||
.Setup(x => x.CreateLogger(It.IsAny<String>()))
|
||||
.Returns(new Mock<ILogger<HopFrameAuthentication>>().Object);
|
||||
|
||||
var encoder = new Mock<UrlEncoder>();
|
||||
var clock = new Mock<ISystemClock>();
|
||||
var tokens = new Mock<ITokenRepository>();
|
||||
var perms = new Mock<IPermissionRepository>();
|
||||
|
||||
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<string>(t => t == correctToken.Content.ToString())))
|
||||
.ReturnsAsync(correctToken);
|
||||
|
||||
perms
|
||||
.Setup(x => x.GetFullPermissions(It.IsAny<User>()))
|
||||
.ReturnsAsync(new List<string>());
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
92
tests/HopFrame.Tests.Security/AuthorizationTests.cs
Normal file
92
tests/HopFrame.Tests.Security/AuthorizationTests.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System.Security.Claims;
|
||||
using HopFrame.Security.Authentication;
|
||||
using HopFrame.Security.Authorization;
|
||||
using HopFrame.Security.Claims;
|
||||
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.Tests.Security;
|
||||
|
||||
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<AuthorizationFilterContext>(MockBehavior.Default, actionContext, new List<IFilterMetadata>());
|
||||
|
||||
context
|
||||
.Setup(x => x.Filters)
|
||||
.Returns(new List<IFilterMetadata>());
|
||||
|
||||
context.SetupProperty(c => c.Result);
|
||||
|
||||
var claims = new List<Claim> {
|
||||
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<UnauthorizedResult>(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<UnauthorizedResult>(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<UnauthorizedResult>(context.Result);
|
||||
}
|
||||
|
||||
}
|
||||
34
tests/HopFrame.Tests.Security/HopFrame.Tests.Security.csproj
Normal file
34
tests/HopFrame.Tests.Security/HopFrame.Tests.Security.csproj
Normal file
@@ -0,0 +1,34 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>disable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
|
||||
<PackageReference Include="Moq" Version="4.20.72" />
|
||||
<PackageReference Include="xunit" Version="2.5.3"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Authentication">
|
||||
<HintPath>..\..\..\..\..\..\..\Program Files\dotnet\shared\Microsoft.AspNetCore.App\8.0.11\Microsoft.AspNetCore.Authentication.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\HopFrame.Security\HopFrame.Security.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
95
tests/HopFrame.Tests.Web/AuthMiddlewareTests.cs
Normal file
95
tests/HopFrame.Tests.Web/AuthMiddlewareTests.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
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.Tests.Web;
|
||||
|
||||
public class AuthMiddlewareTests {
|
||||
private readonly RequestDelegate _delegate = _ => Task.CompletedTask;
|
||||
|
||||
public AuthMiddleware SetupEnvironment(bool isLoggedIn = true, Token newToken = null) {
|
||||
var auth = new Mock<IAuthService>();
|
||||
auth
|
||||
.Setup(a => a.IsLoggedIn())
|
||||
.ReturnsAsync(isLoggedIn);
|
||||
auth
|
||||
.Setup(a => a.RefreshLogin())
|
||||
.ReturnsAsync(newToken);
|
||||
|
||||
var perms = new Mock<IPermissionRepository>();
|
||||
perms
|
||||
.Setup(p => p.GetFullPermissions(It.Is<User>(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<Permission> {
|
||||
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));
|
||||
}
|
||||
}
|
||||
334
tests/HopFrame.Tests.Web/AuthServiceTests.cs
Normal file
334
tests/HopFrame.Tests.Web/AuthServiceTests.cs
Normal file
@@ -0,0 +1,334 @@
|
||||
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.Tests.Web;
|
||||
|
||||
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<IRequestCookieCollection>();
|
||||
cookies
|
||||
.SetupGet(c => c[ITokenContext.RefreshTokenType])
|
||||
.Returns(providedTokenCookie);
|
||||
accessor.HttpContext.Request.Cookies = cookies.Object;
|
||||
}
|
||||
|
||||
var users = new Mock<IUserRepository>();
|
||||
users
|
||||
.Setup(u => u.GetUserByEmail(It.Is<string>(email => CreateDummyUser().Email == email)))
|
||||
.ReturnsAsync(CreateDummyUser());
|
||||
users
|
||||
.Setup(u => u.CheckUserPassword(It.Is<User>(u => u.Email == CreateDummyUser().Email), It.IsAny<string>()))
|
||||
.ReturnsAsync(passwordIsCorrect);
|
||||
users
|
||||
.Setup(u => u.AddUser(It.IsAny<User>()))
|
||||
.ReturnsAsync(CreateDummyUser());
|
||||
users
|
||||
.Setup(u => u.GetUsers())
|
||||
.ReturnsAsync(new List<User> { CreateDummyUser() });
|
||||
|
||||
var tokens = new Mock<ITokenRepository>();
|
||||
tokens
|
||||
.Setup(t => t.CreateToken(It.Is<int>(t => t == Token.RefreshTokenType), It.IsAny<User>()))
|
||||
.ReturnsAsync(new Token {
|
||||
Content = _refreshToken,
|
||||
Type = Token.RefreshTokenType
|
||||
});
|
||||
tokens
|
||||
.Setup(t => t.CreateToken(It.Is<int>(t => t == Token.AccessTokenType), It.IsAny<User>()))
|
||||
.ReturnsAsync(new Token {
|
||||
Content = _accessToken,
|
||||
Type = Token.AccessTokenType
|
||||
});
|
||||
tokens
|
||||
.Setup(t => t.GetToken(It.Is<string>(token => token == _refreshToken.ToString())))
|
||||
.ReturnsAsync(providedRefreshToken);
|
||||
|
||||
var context = new Mock<ITokenContext>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
29
tests/HopFrame.Tests.Web/Extensions/HttpContextExtensions.cs
Normal file
29
tests/HopFrame.Tests.Web/Extensions/HttpContextExtensions.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System.Web;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace HopFrame.Tests.Web.Extensions;
|
||||
|
||||
internal static class HttpContextExtensions {
|
||||
/// <summary>Extracts the partial cookie value from the header section.</summary>
|
||||
/// <param name="headers"><inheritdoc cref="IHeaderDictionary" path="/summary"/></param>
|
||||
/// <param name="key">The key for identifying the cookie.</param>
|
||||
/// <returns>The value of the cookie.</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
29
tests/HopFrame.Tests.Web/HopFrame.Tests.Web.csproj
Normal file
29
tests/HopFrame.Tests.Web/HopFrame.Tests.Web.csproj
Normal file
@@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>disable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="bunit" Version="1.36.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
|
||||
<PackageReference Include="Moq" Version="4.20.72" />
|
||||
<PackageReference Include="xunit" Version="2.5.3"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\HopFrame.Web\HopFrame.Web.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
123
tests/HopFrame.Tests.Web/Pages/AdminLoginTests.cs
Normal file
123
tests/HopFrame.Tests.Web/Pages/AdminLoginTests.cs
Normal file
@@ -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<AdminLogin>, NavigationManager) SetupEnvironment(bool correctCredentials = true) {
|
||||
var auth = new Mock<IAuthService>();
|
||||
auth
|
||||
.Setup(a => a.Login(It.IsAny<UserLogin>()))
|
||||
.ReturnsAsync(correctCredentials);
|
||||
|
||||
Services.AddSweetAlert2();
|
||||
Services.AddBlazorStrap();
|
||||
Services.AddSingleton(auth.Object);
|
||||
var navigator = Services.GetRequiredService<FakeNavigationManager>();
|
||||
|
||||
var component = RenderComponent<AdminLogin>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
133
tests/HopFrame.Tests.Web/Pages/AuthorizedViewTests.cs
Normal file
133
tests/HopFrame.Tests.Web/Pages/AuthorizedViewTests.cs
Normal file
@@ -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 = "<p>Inner Render</p>";
|
||||
|
||||
public NavigationManager SetupEnvironment(bool authenticated = true, params string[] userPermissions) {
|
||||
var auth = new Mock<ITokenContext>();
|
||||
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<IHttpContextAccessor>();
|
||||
accessor
|
||||
.Setup(a => a.HttpContext)
|
||||
.Returns(context);
|
||||
|
||||
Services.AddSingleton(auth.Object);
|
||||
Services.AddSingleton(accessor.Object);
|
||||
return Services.GetRequiredService<FakeNavigationManager>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthorizedView_With_NoValidLogin_And_Redirection_Should_Redirect() {
|
||||
// Arrange
|
||||
var navigator = SetupEnvironment(false);
|
||||
|
||||
// Act
|
||||
RenderComponent<AuthorizedView>(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<AuthorizedView>(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<AuthorizedView>(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<AuthorizedView>(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<AuthorizedView>(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<AuthorizedView>(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<AuthorizedView>(parameters => parameters
|
||||
.AddChildContent(_innerHtml));
|
||||
|
||||
// Assert
|
||||
Assert.DoesNotContain(_innerHtml, component.Markup);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user