Various work around the architecture
This commit is contained in:
parent
b1e134490d
commit
71576a4b1f
18 changed files with 522 additions and 358 deletions
|
|
@ -8,7 +8,7 @@ namespace PlanTempus.Database.Core.ConnectionFactory
|
||||||
{
|
{
|
||||||
private readonly NpgsqlDataSource _baseDataSource;
|
private readonly NpgsqlDataSource _baseDataSource;
|
||||||
private readonly Action<NpgsqlDataSourceBuilder> _configureDataSource;
|
private readonly Action<NpgsqlDataSourceBuilder> _configureDataSource;
|
||||||
private readonly Microsoft.Extensions.Logging.ILoggerFactory _loggerFactory;
|
private readonly Microsoft.Extensions.Logging.ILoggerFactory _loggerFactory; //this is not tested nor implemented, I just created it as an idea
|
||||||
|
|
||||||
public PostgresConnectionFactory(
|
public PostgresConnectionFactory(
|
||||||
string connectionString,
|
string connectionString,
|
||||||
|
|
|
||||||
7
PlanTempus.Components/Class1.cs
Normal file
7
PlanTempus.Components/Class1.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace PlanTempus.Components
|
||||||
|
{
|
||||||
|
public class Class1
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace PlanTempus.Components.Organizations.Create
|
||||||
|
{
|
||||||
|
public class CreateOrganizationCommand
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public Guid CreatedById { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
using Insight.Database;
|
||||||
|
|
||||||
|
namespace PlanTempus.Components.Organizations.Create
|
||||||
|
{
|
||||||
|
public class CreateOrganizationHandler
|
||||||
|
{
|
||||||
|
private readonly IDatabaseOperations _databaseOperations;
|
||||||
|
|
||||||
|
public CreateOrganizationHandler(IDatabaseOperations databaseOperations)
|
||||||
|
{
|
||||||
|
_databaseOperations = databaseOperations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CreateOrganizationResponse> Handle(CreateOrganizationCommand command)
|
||||||
|
{
|
||||||
|
using var db = _databaseOperations.CreateScope(nameof(CreateOrganizationHandler));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var organizationId = Guid.NewGuid();
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
|
||||||
|
var sql = @"
|
||||||
|
INSERT INTO Organizations (Id, Name, Description, CreatedById, CreatedAt, UpdatedAt)
|
||||||
|
VALUES (@Id, @Name, @Description, @CreatedById, @CreatedAt, @UpdatedAt)";
|
||||||
|
|
||||||
|
await db.Connection.ExecuteSqlAsync(sql, new
|
||||||
|
{
|
||||||
|
Id = organizationId,
|
||||||
|
command.Name,
|
||||||
|
command.Description,
|
||||||
|
CreatedById = command.CreatedById,
|
||||||
|
CreatedAt = now,
|
||||||
|
UpdatedAt = now
|
||||||
|
});
|
||||||
|
|
||||||
|
db.Success();
|
||||||
|
|
||||||
|
return new CreateOrganizationResponse
|
||||||
|
{
|
||||||
|
Id = organizationId,
|
||||||
|
Name = command.Name,
|
||||||
|
CreatedAt = now
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
db.Error(ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace PlanTempus.Components.Organizations.Create
|
||||||
|
{
|
||||||
|
public class CreateOrganizationResponse
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
17
PlanTempus.Components/PlanTempus.Components.csproj
Normal file
17
PlanTempus.Components/PlanTempus.Components.csproj
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Core\PlanTempus.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Organizations\Delete\" />
|
||||||
|
<Folder Include="Organizations\Update\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
81
PlanTempus.X.BDD/FeatureFixtures/AccountSecuritySpecs.cs
Normal file
81
PlanTempus.X.BDD/FeatureFixtures/AccountSecuritySpecs.cs
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
using LightBDD.Framework;
|
||||||
|
using LightBDD.Framework.Scenarios;
|
||||||
|
using LightBDD.MsTest3;
|
||||||
|
using PlanTempus.X.Services;
|
||||||
|
using Shouldly;
|
||||||
|
|
||||||
|
namespace PlanTempus.X.BDD.FeatureFixtures;
|
||||||
|
[TestClass]
|
||||||
|
[FeatureDescription(@"As a system administrator
|
||||||
|
I want to ensure account security is maintained
|
||||||
|
So users' data remains protected")]
|
||||||
|
public partial class AccountSecuritySpecs : FeatureFixture
|
||||||
|
{
|
||||||
|
IUserService _userService;
|
||||||
|
IEmailService _emailService;
|
||||||
|
IOrganizationService _organizationService;
|
||||||
|
IAuthService _authService;
|
||||||
|
|
||||||
|
protected User _currentUser;
|
||||||
|
protected DateTime? _lockoutEnd;
|
||||||
|
protected bool _isLocked;
|
||||||
|
protected bool _loginSuccessful;
|
||||||
|
|
||||||
|
public async Task Given_user_exists(string email)
|
||||||
|
{
|
||||||
|
_currentUser = await _userService.GetUserByEmailAsync(email);
|
||||||
|
if (_currentUser == null)
|
||||||
|
{
|
||||||
|
_currentUser = await _userService.CreateUserAsync(email, "Test User");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task When_I_attempt_5_failed_logins_within_5_minutes()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < 5; i++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _authService.AttemptLoginAsync(_currentUser.Email, "WrongPassword");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Expected exception with wrong password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Then_the_account_should_be_locked()
|
||||||
|
{
|
||||||
|
_currentUser = _userService.GetUserByEmail(_currentUser.Email);
|
||||||
|
_isLocked = _currentUser.IsLocked;
|
||||||
|
_isLocked.ShouldBeTrue();
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task And_lockout_end_should_be_set()
|
||||||
|
{
|
||||||
|
_lockoutEnd = _currentUser.LockoutEnd;
|
||||||
|
_lockoutEnd.ShouldNotBeNull();
|
||||||
|
_lockoutEnd.Value.ShouldBeGreaterThan(DateTime.UtcNow);
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task And_subsequent_login_attempts_should_fail_until_lockout_end()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_loginSuccessful = await _authService.AttemptLoginAsync(_currentUser.Email, _currentUser.Password);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_loginSuccessful = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_loginSuccessful.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
73
PlanTempus.X.BDD/FeatureFixtures/EmailConfirmationSpecs.cs
Normal file
73
PlanTempus.X.BDD/FeatureFixtures/EmailConfirmationSpecs.cs
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
using LightBDD.Framework;
|
||||||
|
using LightBDD.Framework.Scenarios;
|
||||||
|
using LightBDD.MsTest3;
|
||||||
|
using PlanTempus.X.Services;
|
||||||
|
using Shouldly;
|
||||||
|
|
||||||
|
namespace PlanTempus.X.BDD.FeatureFixtures;
|
||||||
|
[TestClass]
|
||||||
|
[FeatureDescription(@"As a registered user
|
||||||
|
I want to confirm my email
|
||||||
|
So I can activate my account")]
|
||||||
|
public partial class EmailConfirmationSpecs : FeatureFixture
|
||||||
|
{
|
||||||
|
IUserService _userService;
|
||||||
|
IEmailService _emailService;
|
||||||
|
IOrganizationService _organizationService;
|
||||||
|
|
||||||
|
protected User _currentUser;
|
||||||
|
protected string _currentEmail;
|
||||||
|
protected string _confirmationLink;
|
||||||
|
protected bool _redirectedToWelcome;
|
||||||
|
protected string _errorMessage;
|
||||||
|
|
||||||
|
public async Task Given_a_user_exists_with_unconfirmed_email(string email)
|
||||||
|
{
|
||||||
|
_currentUser = await _userService.CreateUserAsync(email, "Test User");
|
||||||
|
_currentUser.EmailConfirmed.ShouldBeFalse();
|
||||||
|
_currentEmail = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task When_I_click_the_valid_confirmation_link_for(string email)
|
||||||
|
{
|
||||||
|
_confirmationLink = await _emailService.GetConfirmationLinkForEmail(email);
|
||||||
|
await _userService.ConfirmEmailAsync(_confirmationLink);
|
||||||
|
_redirectedToWelcome = true; // Simulate redirect
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Then_the_users_email_confirmed_should_be_true()
|
||||||
|
{
|
||||||
|
_currentUser = _userService.GetUserByEmail(_currentEmail);
|
||||||
|
_currentUser.EmailConfirmed.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task And_I_should_be_redirected_to_the_welcome_page()
|
||||||
|
{
|
||||||
|
_redirectedToWelcome.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task When_I_click_an_invalid_confirmation_link()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _userService.ConfirmEmailAsync("invalid-confirmation-token");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_errorMessage = ex.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Then_I_should_see_an_error_message(string expectedErrorMessage)
|
||||||
|
{
|
||||||
|
_errorMessage.ShouldBe(expectedErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task And_my_email_remains_unconfirmed()
|
||||||
|
{
|
||||||
|
if (_currentUser != null)
|
||||||
|
{
|
||||||
|
_currentUser.EmailConfirmed.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
134
PlanTempus.X.BDD/FeatureFixtures/OrganizationSetupSpecs.cs
Normal file
134
PlanTempus.X.BDD/FeatureFixtures/OrganizationSetupSpecs.cs
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
using LightBDD.Framework;
|
||||||
|
using LightBDD.Framework.Scenarios;
|
||||||
|
using LightBDD.MsTest3;
|
||||||
|
using PlanTempus.X.Services;
|
||||||
|
using Shouldly;
|
||||||
|
|
||||||
|
namespace PlanTempus.X.BDD.FeatureFixtures;
|
||||||
|
[TestClass]
|
||||||
|
[FeatureDescription(@"As a user with confirmed email
|
||||||
|
I want to set up my organization
|
||||||
|
So I can start using the system with my team")]
|
||||||
|
public partial class OrganizationSetupSpecs : FeatureFixture
|
||||||
|
{
|
||||||
|
IUserService _userService;
|
||||||
|
IEmailService _emailService;
|
||||||
|
IOrganizationService _organizationService;
|
||||||
|
IUserOrganizationService _userOrganizationService;
|
||||||
|
ITenantService _tenantService;
|
||||||
|
IAuthService _authService;
|
||||||
|
|
||||||
|
protected User _currentUser;
|
||||||
|
protected Organization _organization;
|
||||||
|
protected Exception _setupError;
|
||||||
|
protected List<Organization> _userOrganizations;
|
||||||
|
|
||||||
|
public async Task Given_user_has_confirmed_their_email(string email)
|
||||||
|
{
|
||||||
|
// Create a user with confirmed email
|
||||||
|
_currentUser = await _userService.CreateUserAsync(email, "Test User");
|
||||||
|
var confirmationLink = await _emailService.GetConfirmationLinkForEmail(email);
|
||||||
|
await _userService.ConfirmEmailAsync(confirmationLink);
|
||||||
|
_currentUser.EmailConfirmed.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task When_user_submit_organization_name_and_valid_password(string orgName, string password)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_organization = await _organizationService.SetupOrganizationAsync(_currentUser.Id, orgName, password);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_setupError = ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Then_a_new_organization_should_be_created_with_expected_properties()
|
||||||
|
{
|
||||||
|
_organization.ShouldNotBeNull();
|
||||||
|
_organization.Name.ShouldBe("Acme Corp");
|
||||||
|
_organization.CreatedBy.ShouldBe(_currentUser.Id);
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task And_the_user_should_be_linked_to_the_organization_in_user_organizations()
|
||||||
|
{
|
||||||
|
var userOrg = _userOrganizationService.GetUserOrganization(_currentUser.Id, _organization.Id);
|
||||||
|
userOrg.ShouldNotBeNull();
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task And_tenant_tables_should_be_created_for_the_organization()
|
||||||
|
{
|
||||||
|
var tenantTablesExist = _tenantService.ValidateTenantTablesExist(_organization.Id);
|
||||||
|
tenantTablesExist.ShouldBeTrue();
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task And_user_should_be_logged_into_the_system()
|
||||||
|
{
|
||||||
|
var isAuthenticated = _authService.IsUserAuthenticated(_currentUser.Id);
|
||||||
|
isAuthenticated.ShouldBeTrue();
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task When_user_submit_organization_name_without_password(string orgName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _organizationService.SetupOrganizationAsync(_currentUser.Id, orgName, "");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_setupError = ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Then_organization_setup_should_fail_with_error(string expectedErrorMessage)
|
||||||
|
{
|
||||||
|
_setupError.ShouldNotBeNull();
|
||||||
|
_setupError.Message.ShouldBe(expectedErrorMessage);
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Given_user_has_completed_initial_setup(string email)
|
||||||
|
{
|
||||||
|
await Given_user_has_confirmed_their_email(email);
|
||||||
|
await When_user_submit_organization_name_and_valid_password("First Org", "ValidP@ssw0rd");
|
||||||
|
_userOrganizations = new List<Organization> { _organization };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task When_user_create_a_new_organization(string orgName)
|
||||||
|
{
|
||||||
|
var newOrg = await _organizationService.CreateOrganizationAsync(_currentUser.Id, orgName);
|
||||||
|
_userOrganizations.Add(newOrg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Then_a_new_organization_entry_should_be_created()
|
||||||
|
{
|
||||||
|
_userOrganizations.Count.ShouldBe(2);
|
||||||
|
_userOrganizations[1].Name.ShouldBe("Second Org");
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task And_the_user_should_be_linked_to_both_organizations()
|
||||||
|
{
|
||||||
|
var userOrgs = _userOrganizationService.GetUserOrganizations(_currentUser.Id);
|
||||||
|
userOrgs.Count.ShouldBe(2);
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -53,17 +53,23 @@ public partial class UserRegistrationSpecs : FeatureFixture
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Then_a_new_user_should_be_created_with_email_and_confirmation_status(string email, bool confirmationStatus)
|
public async Task Then_a_new_user_should_be_created_with_email_and_confirmation_status(string email, bool confirmationStatus)
|
||||||
{
|
{
|
||||||
_currentUser.ShouldNotBeNull();
|
_currentUser.ShouldNotBeNull();
|
||||||
_currentUser.Email.ShouldBe(email);
|
_currentUser.Email.ShouldBe(email);
|
||||||
_currentUser.EmailConfirmed.ShouldBe(confirmationStatus);
|
_currentUser.EmailConfirmed.ShouldBe(confirmationStatus);
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Then_a_confirmation_email_should_be_sent()
|
public async Task Then_a_confirmation_email_should_be_sent()
|
||||||
{
|
{
|
||||||
var emailSent = _emailService.WasConfirmationEmailSent(_currentEmail);
|
var emailSent = _emailService.WasConfirmationEmailSent(_currentEmail);
|
||||||
emailSent.ShouldBeTrue();
|
emailSent.ShouldBeTrue();
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Given_a_user_already_exists_with_email(string email)
|
public async Task Given_a_user_already_exists_with_email(string email)
|
||||||
|
|
@ -72,262 +78,17 @@ public partial class UserRegistrationSpecs : FeatureFixture
|
||||||
_currentUser = await _userService.CreateUserAsync(email, "Existing User");
|
_currentUser = await _userService.CreateUserAsync(email, "Existing User");
|
||||||
_currentUser.ShouldNotBeNull();
|
_currentUser.ShouldNotBeNull();
|
||||||
_currentEmail = email;
|
_currentEmail = email;
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Then_registration_should_fail_with_error(string expectedErrorMessage)
|
public async Task Then_registration_should_fail_with_error(string expectedErrorMessage)
|
||||||
{
|
{
|
||||||
_registrationError.ShouldNotBeNull();
|
_registrationError.ShouldNotBeNull();
|
||||||
_registrationError.Message.ShouldBe(expectedErrorMessage);
|
_registrationError.Message.ShouldBe(expectedErrorMessage);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
|
|
||||||
[TestClass]
|
|
||||||
[FeatureDescription(@"As a registered user
|
|
||||||
I want to confirm my email
|
|
||||||
So I can activate my account")]
|
|
||||||
public partial class EmailConfirmationSpecs : FeatureFixture
|
|
||||||
{
|
|
||||||
IUserService _userService;
|
|
||||||
IEmailService _emailService;
|
|
||||||
IOrganizationService _organizationService;
|
|
||||||
|
|
||||||
protected User _currentUser;
|
|
||||||
protected string _currentEmail;
|
|
||||||
protected string _confirmationLink;
|
|
||||||
protected bool _redirectedToWelcome;
|
|
||||||
protected string _errorMessage;
|
|
||||||
|
|
||||||
public async Task Given_a_user_exists_with_unconfirmed_email(string email)
|
|
||||||
{
|
|
||||||
_currentUser = await _userService.CreateUserAsync(email, "Test User");
|
|
||||||
_currentUser.EmailConfirmed.ShouldBeFalse();
|
|
||||||
_currentEmail = email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task When_I_click_the_valid_confirmation_link_for(string email)
|
|
||||||
{
|
|
||||||
_confirmationLink = await _emailService.GetConfirmationLinkForEmail(email);
|
|
||||||
await _userService.ConfirmEmailAsync(_confirmationLink);
|
|
||||||
_redirectedToWelcome = true; // Simulate redirect
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Then_the_users_email_confirmed_should_be_true()
|
|
||||||
{
|
|
||||||
_currentUser = _userService.GetUserByEmail(_currentEmail);
|
|
||||||
_currentUser.EmailConfirmed.ShouldBeTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void And_I_should_be_redirected_to_the_welcome_page()
|
|
||||||
{
|
|
||||||
_redirectedToWelcome.ShouldBeTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task When_I_click_an_invalid_confirmation_link()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _userService.ConfirmEmailAsync("invalid-confirmation-token");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_errorMessage = ex.Message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Then_I_should_see_an_error_message(string expectedErrorMessage)
|
|
||||||
{
|
|
||||||
_errorMessage.ShouldBe(expectedErrorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void And_my_email_remains_unconfirmed()
|
|
||||||
{
|
|
||||||
if (_currentUser != null)
|
|
||||||
{
|
|
||||||
_currentUser.EmailConfirmed.ShouldBeFalse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[TestClass]
|
|
||||||
[FeatureDescription(@"As a user with confirmed email
|
|
||||||
I want to set up my organization
|
|
||||||
So I can start using the system with my team")]
|
|
||||||
public partial class OrganizationSetupSpecs : FeatureFixture
|
|
||||||
{
|
|
||||||
IUserService _userService;
|
|
||||||
IEmailService _emailService;
|
|
||||||
IOrganizationService _organizationService;
|
|
||||||
IUserOrganizationService _userOrganizationService;
|
|
||||||
ITenantService _tenantService;
|
|
||||||
IAuthService _authService;
|
|
||||||
|
|
||||||
protected User _currentUser;
|
|
||||||
protected Organization _organization;
|
|
||||||
protected Exception _setupError;
|
|
||||||
protected List<Organization> _userOrganizations;
|
|
||||||
|
|
||||||
public async Task Given_user_has_confirmed_their_email(string email)
|
|
||||||
{
|
|
||||||
// Create a user with confirmed email
|
|
||||||
_currentUser = await _userService.CreateUserAsync(email, "Test User");
|
|
||||||
var confirmationLink = await _emailService.GetConfirmationLinkForEmail(email);
|
|
||||||
await _userService.ConfirmEmailAsync(confirmationLink);
|
|
||||||
_currentUser.EmailConfirmed.ShouldBeTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task When_I_submit_organization_name_and_valid_password(string orgName, string password)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_organization = await _organizationService.SetupOrganizationAsync(_currentUser.Id, orgName, password);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_setupError = ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Then_a_new_organization_should_be_created_with_expected_properties()
|
|
||||||
{
|
|
||||||
_organization.ShouldNotBeNull();
|
|
||||||
_organization.Name.ShouldBe("Acme Corp");
|
|
||||||
_organization.CreatedBy.ShouldBe(_currentUser.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void And_the_user_should_be_linked_to_the_organization_in_user_organizations()
|
|
||||||
{
|
|
||||||
var userOrg = _userOrganizationService.GetUserOrganization(_currentUser.Id, _organization.Id);
|
|
||||||
userOrg.ShouldNotBeNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void And_tenant_tables_should_be_created_for_the_organization()
|
|
||||||
{
|
|
||||||
var tenantTablesExist = _tenantService.ValidateTenantTablesExist(_organization.Id);
|
|
||||||
tenantTablesExist.ShouldBeTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void And_I_should_be_logged_into_the_system()
|
|
||||||
{
|
|
||||||
var isAuthenticated = _authService.IsUserAuthenticated(_currentUser.Id);
|
|
||||||
isAuthenticated.ShouldBeTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task When_I_submit_organization_name_without_password(string orgName)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _organizationService.SetupOrganizationAsync(_currentUser.Id, orgName, "");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_setupError = ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Then_organization_setup_should_fail_with_error(string expectedErrorMessage)
|
|
||||||
{
|
|
||||||
_setupError.ShouldNotBeNull();
|
|
||||||
_setupError.Message.ShouldBe(expectedErrorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Given_user_has_completed_initial_setup(string email)
|
|
||||||
{
|
|
||||||
await Given_user_has_confirmed_their_email(email);
|
|
||||||
await When_I_submit_organization_name_and_valid_password("First Org", "ValidP@ssw0rd");
|
|
||||||
_userOrganizations = new List<Organization> { _organization };
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task When_I_create_a_new_organization(string orgName)
|
|
||||||
{
|
|
||||||
var newOrg = await _organizationService.CreateOrganizationAsync(_currentUser.Id, orgName);
|
|
||||||
_userOrganizations.Add(newOrg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Then_a_new_organization_entry_should_be_created()
|
|
||||||
{
|
|
||||||
_userOrganizations.Count.ShouldBe(2);
|
|
||||||
_userOrganizations[1].Name.ShouldBe("Second Org");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void And_the_user_should_be_linked_to_both_organizations()
|
|
||||||
{
|
|
||||||
var userOrgs = _userOrganizationService.GetUserOrganizations(_currentUser.Id);
|
|
||||||
userOrgs.Count.ShouldBe(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[TestClass]
|
|
||||||
[FeatureDescription(@"As a system administrator
|
|
||||||
I want to ensure account security is maintained
|
|
||||||
So users' data remains protected")]
|
|
||||||
public partial class AccountSecuritySpecs : FeatureFixture
|
|
||||||
{
|
|
||||||
IUserService _userService;
|
|
||||||
IEmailService _emailService;
|
|
||||||
IOrganizationService _organizationService;
|
|
||||||
IAuthService _authService;
|
|
||||||
|
|
||||||
protected User _currentUser;
|
|
||||||
protected DateTime? _lockoutEnd;
|
|
||||||
protected bool _isLocked;
|
|
||||||
protected bool _loginSuccessful;
|
|
||||||
|
|
||||||
public async Task Given_user_exists(string email)
|
|
||||||
{
|
|
||||||
_currentUser = await _userService.GetUserByEmailAsync(email);
|
|
||||||
if (_currentUser == null)
|
|
||||||
{
|
|
||||||
_currentUser = await _userService.CreateUserAsync(email, "Test User");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task When_I_attempt_5_failed_logins_within_5_minutes()
|
|
||||||
{
|
|
||||||
for (var i = 0; i < 5; i++)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _authService.AttemptLoginAsync(_currentUser.Email, "WrongPassword");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Expected exception with wrong password
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Then_the_account_should_be_locked()
|
|
||||||
{
|
|
||||||
_currentUser = _userService.GetUserByEmail(_currentUser.Email);
|
|
||||||
_isLocked = _currentUser.IsLocked;
|
|
||||||
_isLocked.ShouldBeTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void And_lockout_end_should_be_set()
|
|
||||||
{
|
|
||||||
_lockoutEnd = _currentUser.LockoutEnd;
|
|
||||||
_lockoutEnd.ShouldNotBeNull();
|
|
||||||
_lockoutEnd.Value.ShouldBeGreaterThan(DateTime.UtcNow);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task And_subsequent_login_attempts_should_fail_until_lockout_end()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_loginSuccessful = await _authService.AttemptLoginAsync(_currentUser.Email, _currentUser.Password);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
_loginSuccessful = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_loginSuccessful.ShouldBeFalse();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
21
PlanTempus.X.BDD/Scenarios/AccountSecuritySpecs.cs
Normal file
21
PlanTempus.X.BDD/Scenarios/AccountSecuritySpecs.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
using LightBDD.Framework;
|
||||||
|
using LightBDD.Framework.Scenarios;
|
||||||
|
using LightBDD.MsTest3;
|
||||||
|
|
||||||
|
namespace PlanTempus.X.BDD.Scenarios;
|
||||||
|
[TestClass]
|
||||||
|
public partial class AccountSecuritySpecs : FeatureFixtures.AccountSecuritySpecs
|
||||||
|
{
|
||||||
|
[Scenario]
|
||||||
|
[TestMethod]
|
||||||
|
public async Task User_lockout_after_multiple_failed_attempts()
|
||||||
|
{
|
||||||
|
await Runner.RunScenarioAsync(
|
||||||
|
_ => Given_user_exists("test@example.com"),
|
||||||
|
_ => When_I_attempt_5_failed_logins_within_5_minutes(),
|
||||||
|
_ => Then_the_account_should_be_locked(),
|
||||||
|
_ => And_lockout_end_should_be_set(),
|
||||||
|
_ => And_subsequent_login_attempts_should_fail_until_lockout_end()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
31
PlanTempus.X.BDD/Scenarios/EmailConfirmationSpecs.cs
Normal file
31
PlanTempus.X.BDD/Scenarios/EmailConfirmationSpecs.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
using LightBDD.Framework;
|
||||||
|
using LightBDD.Framework.Scenarios;
|
||||||
|
using LightBDD.MsTest3;
|
||||||
|
|
||||||
|
namespace PlanTempus.X.BDD.Scenarios;
|
||||||
|
[TestClass]
|
||||||
|
public partial class EmailConfirmationSpecs : FeatureFixtures.EmailConfirmationSpecs
|
||||||
|
{
|
||||||
|
[Scenario]
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Confirm_valid_email_address()
|
||||||
|
{
|
||||||
|
await Runner.RunScenarioAsync(
|
||||||
|
_ => Given_a_user_exists_with_unconfirmed_email("test@example.com"),
|
||||||
|
_ => When_I_click_the_valid_confirmation_link_for("test@example.com"),
|
||||||
|
_ => Then_the_users_email_confirmed_should_be_true(),
|
||||||
|
_ => And_I_should_be_redirected_to_the_welcome_page()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Scenario]
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Handle_invalid_confirmation_link()
|
||||||
|
{
|
||||||
|
await Runner.RunScenarioAsync(
|
||||||
|
_ => When_I_click_an_invalid_confirmation_link(),
|
||||||
|
_ => Then_I_should_see_an_error_message("Invalid confirmation link"),
|
||||||
|
_ => And_my_email_remains_unconfirmed()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
45
PlanTempus.X.BDD/Scenarios/OrganizationSetupSpecs.cs
Normal file
45
PlanTempus.X.BDD/Scenarios/OrganizationSetupSpecs.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
using LightBDD.Framework;
|
||||||
|
using LightBDD.Framework.Scenarios;
|
||||||
|
using LightBDD.MsTest3;
|
||||||
|
|
||||||
|
namespace PlanTempus.X.BDD.Scenarios;
|
||||||
|
[TestClass]
|
||||||
|
public partial class OrganizationSetupSpecs : FeatureFixtures.OrganizationSetupSpecs
|
||||||
|
{
|
||||||
|
[Scenario]
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Complete_organization_setup_after_confirmation()
|
||||||
|
{
|
||||||
|
await Runner.RunScenarioAsync(
|
||||||
|
_ => Given_user_has_confirmed_their_email("test@example.com"),
|
||||||
|
_ => When_user_submit_organization_name_and_valid_password("Acme Corp", "ValidP@ssw0rd"),
|
||||||
|
_ => Then_a_new_organization_should_be_created_with_expected_properties(),
|
||||||
|
_ => And_the_user_should_be_linked_to_the_organization_in_user_organizations(),
|
||||||
|
_ => And_tenant_tables_should_be_created_for_the_organization(),
|
||||||
|
_ => And_user_should_be_logged_into_the_system()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Scenario]
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Prevent_organization_setup_without_password()
|
||||||
|
{
|
||||||
|
await Runner.RunScenarioAsync(
|
||||||
|
_ => Given_user_has_confirmed_their_email("test@example.com"),
|
||||||
|
_ => When_user_submit_organization_name_without_password("Acme Corp"),
|
||||||
|
_ => Then_organization_setup_should_fail_with_error("Password required")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Scenario]
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Handle_multiple_organization_creations()
|
||||||
|
{
|
||||||
|
await Runner.RunScenarioAsync(
|
||||||
|
_ => Given_user_has_completed_initial_setup("test@example.com"),
|
||||||
|
_ => When_user_create_a_new_organization("Second Org"),
|
||||||
|
_ => Then_a_new_organization_entry_should_be_created(),
|
||||||
|
_ => And_the_user_should_be_linked_to_both_organizations()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -29,91 +29,3 @@ public partial class UserRegistrationSpecs : FeatureFixtures.UserRegistrationSpe
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[TestClass]
|
|
||||||
public partial class EmailConfirmationSpecs : FeatureFixtures.EmailConfirmationSpecs
|
|
||||||
{
|
|
||||||
[Scenario]
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Confirm_valid_email_address()
|
|
||||||
{
|
|
||||||
await Runner.RunScenarioAsync(
|
|
||||||
_ => Given_a_user_exists_with_unconfirmed_email("test@example.com"),
|
|
||||||
_ => When_I_click_the_valid_confirmation_link_for("test@example.com"),
|
|
||||||
_ => Then_the_users_email_confirmed_should_be_true(),
|
|
||||||
_ => And_I_should_be_redirected_to_the_welcome_page()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Scenario]
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Handle_invalid_confirmation_link()
|
|
||||||
{
|
|
||||||
await Runner.RunScenarioAsync(
|
|
||||||
_ => When_I_click_an_invalid_confirmation_link(),
|
|
||||||
_ => Then_I_should_see_an_error_message("Invalid confirmation link"),
|
|
||||||
_ => And_my_email_remains_unconfirmed()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[TestClass]
|
|
||||||
public partial class OrganizationSetupSpecs : FeatureFixtures.OrganizationSetupSpecs
|
|
||||||
{
|
|
||||||
[Scenario]
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Complete_organization_setup_after_confirmation()
|
|
||||||
{
|
|
||||||
await Runner.RunScenarioAsync(
|
|
||||||
_ => Given_user_has_confirmed_their_email("test@example.com"),
|
|
||||||
_ => When_I_submit_organization_name_and_valid_password("Acme Corp", "ValidP@ssw0rd"),
|
|
||||||
_ => Then_a_new_organization_should_be_created_with_expected_properties(),
|
|
||||||
_ => And_the_user_should_be_linked_to_the_organization_in_user_organizations(),
|
|
||||||
_ => And_tenant_tables_should_be_created_for_the_organization(),
|
|
||||||
_ => And_I_should_be_logged_into_the_system()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Scenario]
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Prevent_organization_setup_without_password()
|
|
||||||
{
|
|
||||||
await Runner.RunScenarioAsync(
|
|
||||||
_ => Given_user_has_confirmed_their_email("test@example.com"),
|
|
||||||
_ => When_I_submit_organization_name_without_password("Acme Corp"),
|
|
||||||
_ => Then_organization_setup_should_fail_with_error("Password required")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Scenario]
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Handle_multiple_organization_creations()
|
|
||||||
{
|
|
||||||
await Runner.RunScenarioAsync(
|
|
||||||
_ => Given_user_has_completed_initial_setup("test@example.com"),
|
|
||||||
_ => When_I_create_a_new_organization("Second Org"),
|
|
||||||
_ => Then_a_new_organization_entry_should_be_created(),
|
|
||||||
_ => And_the_user_should_be_linked_to_both_organizations()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[TestClass]
|
|
||||||
public partial class AccountSecuritySpecs : FeatureFixtures.AccountSecuritySpecs
|
|
||||||
{
|
|
||||||
[Scenario]
|
|
||||||
[TestMethod]
|
|
||||||
public async Task User_lockout_after_multiple_failed_attempts()
|
|
||||||
{
|
|
||||||
await Runner.RunScenarioAsync(
|
|
||||||
_ => Given_user_exists("test@example.com"),
|
|
||||||
_ => When_I_attempt_5_failed_logins_within_5_minutes(),
|
|
||||||
_ => Then_the_account_should_be_locked(),
|
|
||||||
_ => And_lockout_end_should_be_set(),
|
|
||||||
_ => And_subsequent_login_attempts_should_fail_until_lockout_end()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -14,6 +14,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlanTempus.SetupInfrastruct
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlanTempus.X.BDD", "PlanTempus.X.BDD\PlanTempus.X.BDD.csproj", "{8CA2246B-7D8C-40DA-9042-CA17A7A7672B}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlanTempus.X.BDD", "PlanTempus.X.BDD\PlanTempus.X.BDD.csproj", "{8CA2246B-7D8C-40DA-9042-CA17A7A7672B}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlanTempus.Components", "PlanTempus.Components\PlanTempus.Components.csproj", "{ECC8621A-7B3F-4E26-85A1-926FA263E5D7}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -32,10 +34,6 @@ Global
|
||||||
{111CE8AE-E637-4376-A5A3-88D33E77EA88}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{111CE8AE-E637-4376-A5A3-88D33E77EA88}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{111CE8AE-E637-4376-A5A3-88D33E77EA88}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{111CE8AE-E637-4376-A5A3-88D33E77EA88}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{111CE8AE-E637-4376-A5A3-88D33E77EA88}.Release|Any CPU.Build.0 = Release|Any CPU
|
{111CE8AE-E637-4376-A5A3-88D33E77EA88}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{8CA2246B-7D8C-40DA-9042-CA17A7A7672B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{8CA2246B-7D8C-40DA-9042-CA17A7A7672B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{8CA2246B-7D8C-40DA-9042-CA17A7A7672B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{8CA2246B-7D8C-40DA-9042-CA17A7A7672B}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{D5096A7F-E6D4-4C87-874E-2D9A6BEAD57F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{D5096A7F-E6D4-4C87-874E-2D9A6BEAD57F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{D5096A7F-E6D4-4C87-874E-2D9A6BEAD57F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{D5096A7F-E6D4-4C87-874E-2D9A6BEAD57F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{D5096A7F-E6D4-4C87-874E-2D9A6BEAD57F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{D5096A7F-E6D4-4C87-874E-2D9A6BEAD57F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
|
@ -44,6 +42,14 @@ Global
|
||||||
{48300227-BCBB-45A3-8359-9064DA85B1F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{48300227-BCBB-45A3-8359-9064DA85B1F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{48300227-BCBB-45A3-8359-9064DA85B1F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{48300227-BCBB-45A3-8359-9064DA85B1F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{48300227-BCBB-45A3-8359-9064DA85B1F9}.Release|Any CPU.Build.0 = Release|Any CPU
|
{48300227-BCBB-45A3-8359-9064DA85B1F9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8CA2246B-7D8C-40DA-9042-CA17A7A7672B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8CA2246B-7D8C-40DA-9042-CA17A7A7672B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8CA2246B-7D8C-40DA-9042-CA17A7A7672B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8CA2246B-7D8C-40DA-9042-CA17A7A7672B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{ECC8621A-7B3F-4E26-85A1-926FA263E5D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{ECC8621A-7B3F-4E26-85A1-926FA263E5D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{ECC8621A-7B3F-4E26-85A1-926FA263E5D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{ECC8621A-7B3F-4E26-85A1-926FA263E5D7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"resources":{"Scripts/Script-1.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptmain"},"Scripts/Script.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptmain"},"Scripts/SmartConfigSystem.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptdb01"},"Scripts/grant-privileges.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptmain"}}}
|
{"resources":{"Scripts/Script-1.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptmain"},"Scripts/Script.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptmain"},"Scripts/SmartConfigSystem.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptdb01"},"Scripts/grant-privileges.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44"}}}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"resources":{"Scripts/Script-1.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptmain"},"Scripts/Script.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptmain"},"Scripts/SmartConfigSystem.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptdb01"},"Scripts/grant-privileges.sql":{"default-schema":"system","default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"sandbox"}}}
|
{"resources":{"Scripts/Script-1.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptmain"},"Scripts/Script.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptmain"},"Scripts/SmartConfigSystem.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptdb01"},"Scripts/grant-privileges.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptmain"}}}
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
using Insight.Database;
|
using Insight.Database;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using FluentAssertions;
|
|
||||||
using Autofac;
|
using Autofac;
|
||||||
|
using Shouldly;
|
||||||
using PlanTempus.Database.Core.ConnectionFactory;
|
using PlanTempus.Database.Core.ConnectionFactory;
|
||||||
|
|
||||||
namespace PlanTempus.Tests.ConfigurationSystem;
|
namespace PlanTempus.Tests.ConfigurationSystem;
|
||||||
|
|
@ -64,7 +64,7 @@ public class SetupConfigurationTests : TestFixture
|
||||||
action_type = "I"
|
action_type = "I"
|
||||||
});
|
});
|
||||||
var actual = JsonConvert.SerializeObject(history) as string;
|
var actual = JsonConvert.SerializeObject(history) as string;
|
||||||
actual.Should().Be(expected);
|
actual.ShouldBe(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|
@ -95,7 +95,7 @@ public class SetupConfigurationTests : TestFixture
|
||||||
.Single();
|
.Single();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
((DateTime)updated.modified_at).Should().BeAfter((DateTime)original.modified_at);
|
((DateTime)updated.modified_at).ShouldBeGreaterThan((DateTime)original.modified_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|
@ -134,7 +134,7 @@ public class SetupConfigurationTests : TestFixture
|
||||||
action_type = "D"
|
action_type = "D"
|
||||||
});
|
});
|
||||||
var actual = JsonConvert.SerializeObject(history) as string;
|
var actual = JsonConvert.SerializeObject(history) as string;
|
||||||
actual.Should().Be(expected);
|
actual.ShouldBe(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue