Migrate from User to Account domain concept

Renames core domain entities and services from "User" to "Account"

Refactors project-wide namespaces, classes, and database tables to use "Account" terminology
Updates related components, services, and database schema to reflect new domain naming
Standardizes naming conventions across authentication and organization setup features
This commit is contained in:
Janus C. H. Knudsen 2026-01-09 22:14:46 +01:00
parent e5e7c1c19f
commit 88812177a9
29 changed files with 288 additions and 298 deletions

2
.gitignore vendored
View file

@ -360,3 +360,5 @@ MigrationBackup/
# Fody - auto-generated XML schema # Fody - auto-generated XML schema
FodyWeavers.xsd FodyWeavers.xsd
nul

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"liveServer.settings.port": 5501
}

View file

@ -1,12 +1,6 @@
using System; namespace PlanTempus.Core.Entities.Accounts
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PlanTempus.Core.Entities.Users
{ {
public class User public class Account
{ {
public int Id { get; set; } public int Id { get; set; }
public string Email { get; set; } public string Email { get; set; }
@ -26,9 +20,9 @@ namespace PlanTempus.Core.Entities.Users
public bool IsActive { get; set; } public bool IsActive { get; set; }
} }
public class UserOrganization public class AccountOrganization
{ {
public int UserId { get; set; } public int AccountId { get; set; }
public int OrganizationId { get; set; } public int OrganizationId { get; set; }
public DateTime CreatedDate { get; set; } public DateTime CreatedDate { get; set; }
} }

View file

@ -1,24 +1,24 @@
using Insight.Database; using Insight.Database;
using PlanTempus.Core; using PlanTempus.Core;
using PlanTempus.Core.Entities.Users; using PlanTempus.Core.Entities.Accounts;
using System.Data; using System.Data;
namespace PlanTempus.Database.Core namespace PlanTempus.Database.Core
{ {
public class UserService public class AccountService
{ {
public record UserCreateCommand(string CorrelationId, string Email, string Password); public record AccountCreateCommand(string CorrelationId, string Email, string Password);
private readonly IDbConnection _db; private readonly IDbConnection _db;
public UserService(IDbConnection db) public AccountService(IDbConnection db)
{ {
_db = db; _db = db;
} }
public async Task CreateUser(UserCreateCommand command) public async Task CreateAccount(AccountCreateCommand command)
{ {
var user = new User var account = new Account
{ {
Email = command.Email, Email = command.Email,
PasswordHash = new SecureTokenizer().TokenizeText(command.Password), PasswordHash = new SecureTokenizer().TokenizeText(command.Password),
@ -27,13 +27,13 @@ namespace PlanTempus.Database.Core
CreatedDate = DateTime.UtcNow CreatedDate = DateTime.UtcNow
}; };
var userId = await _db.ExecuteScalarAsync<int>(@$" var accountId = await _db.ExecuteScalarAsync<int>(@$"
INSERT INTO users (email, password_hash, security_stamp, email_confirmed, created_at) INSERT INTO accounts (email, password_hash, security_stamp, email_confirmed, created_at)
VALUES (@Email, @PasswordHash, @SecurityStamp, @EmailConfirmed, @CreatedDate) VALUES (@Email, @PasswordHash, @SecurityStamp, @EmailConfirmed, @CreatedDate)
RETURNING id", user); RETURNING id", account);
} }
public async Task CreateOrganization(int userId, string organizationConnectionString) public async Task CreateOrganization(int accountId, string organizationConnectionString)
{ {
var schema = "dev"; var schema = "dev";
@ -46,7 +46,7 @@ namespace PlanTempus.Database.Core
{ {
ConnectionString = organizationConnectionString, ConnectionString = organizationConnectionString,
CreatedDate = DateTime.UtcNow, CreatedDate = DateTime.UtcNow,
CreatedBy = userId, CreatedBy = accountId,
IsActive = true IsActive = true
}; };
@ -55,17 +55,17 @@ namespace PlanTempus.Database.Core
VALUES (@ConnectionString, @CreatedDate, @IsActive) VALUES (@ConnectionString, @CreatedDate, @IsActive)
RETURNING id", organization); RETURNING id", organization);
// Link user to organization // Link account to organization
var userOrganization = new UserOrganization var accountOrganization = new AccountOrganization
{ {
UserId = userId, AccountId = accountId,
OrganizationId = organizationId, OrganizationId = organizationId,
CreatedDate = DateTime.UtcNow CreatedDate = DateTime.UtcNow
}; };
await _db.ExecuteAsync(@$" await _db.ExecuteAsync(@$"
INSERT INTO {schema}.user_organizations (user_id, organization_id, created_date) INSERT INTO {schema}.account_organizations (account_id, organization_id, created_date)
VALUES (@UserId, @OrganizationId, @CreatedDate)", userOrganization); VALUES (@AccountId, @OrganizationId, @CreatedDate)", accountOrganization);
transaction.Commit(); transaction.Commit();
} }

View file

@ -35,9 +35,9 @@ namespace PlanTempus.Database.Core.DDL
using var transaction = conn.OpenWithTransaction(); using var transaction = conn.OpenWithTransaction();
try try
{ {
CreateUsersTable(conn); CreateAccountsTable(conn);
CreateOrganizationsTable(conn); CreateOrganizationsTable(conn);
CreateUserOrganizationsTable(conn); CreateAccountOrganizationsTable(conn);
SetupRLS(conn); SetupRLS(conn);
transaction.Commit(); transaction.Commit();
@ -51,12 +51,12 @@ namespace PlanTempus.Database.Core.DDL
/// <summary> /// <summary>
/// Creates the users table /// Creates the accounts table
/// </summary> /// </summary>
void CreateUsersTable(IDbConnection db) void CreateAccountsTable(IDbConnection db)
{ {
var sql = @$" var sql = @$"
CREATE TABLE IF NOT EXISTS {_command.Schema}.users ( CREATE TABLE IF NOT EXISTS {_command.Schema}.accounts (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
email VARCHAR(256) NOT NULL UNIQUE, email VARCHAR(256) NOT NULL UNIQUE,
password_hash VARCHAR(256) NOT NULL, password_hash VARCHAR(256) NOT NULL,
@ -91,17 +91,17 @@ namespace PlanTempus.Database.Core.DDL
} }
/// <summary> /// <summary>
/// Creates the user_organizations table /// Creates the account_organizations table
/// </summary> /// </summary>
void CreateUserOrganizationsTable(IDbConnection db) void CreateAccountOrganizationsTable(IDbConnection db)
{ {
var sql = @$" var sql = @$"
CREATE TABLE IF NOT EXISTS {_command.Schema}.user_organizations ( CREATE TABLE IF NOT EXISTS {_command.Schema}.account_organizations (
user_id INTEGER NOT NULL REFERENCES {_command.Schema}.users(id), account_id INTEGER NOT NULL REFERENCES {_command.Schema}.accounts(id),
organization_id INTEGER NOT NULL REFERENCES {_command.Schema}.organizations(id), organization_id INTEGER NOT NULL REFERENCES {_command.Schema}.organizations(id),
pin_code VARCHAR(10) NULL, pin_code VARCHAR(10) NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, organization_id) PRIMARY KEY (account_id, organization_id)
);"; );";
db.ExecuteSql(sql); db.ExecuteSql(sql);
@ -109,24 +109,24 @@ namespace PlanTempus.Database.Core.DDL
} }
/// <summary> /// <summary>
/// Sets up Row Level Security (RLS) for the organizations and user_organizations tables. /// Sets up Row Level Security (RLS) for the organizations and account_organizations tables.
/// </summary> /// </summary>
void SetupRLS(IDbConnection db) void SetupRLS(IDbConnection db)
{ {
var sql = new[] var sql = new[]
{ {
$"ALTER TABLE {_command.Schema}.organizations ENABLE ROW LEVEL SECURITY;", $"ALTER TABLE {_command.Schema}.organizations ENABLE ROW LEVEL SECURITY;",
$"ALTER TABLE {_command.Schema}.user_organizations ENABLE ROW LEVEL SECURITY;", $"ALTER TABLE {_command.Schema}.account_organizations ENABLE ROW LEVEL SECURITY;",
$"DROP POLICY IF EXISTS organization_access ON {_command.Schema}.organizations;", $"DROP POLICY IF EXISTS organization_access ON {_command.Schema}.organizations;",
@$"CREATE POLICY organization_access ON {_command.Schema}.organizations @$"CREATE POLICY organization_access ON {_command.Schema}.organizations
USING (id IN ( USING (id IN (
SELECT organization_id SELECT organization_id
FROM {_command.Schema}.user_organizations FROM {_command.Schema}.account_organizations
WHERE user_id = current_setting('app.user_id', TRUE)::INTEGER WHERE account_id = current_setting('app.account_id', TRUE)::INTEGER
)) WITH CHECK (true);", )) WITH CHECK (true);",
$"DROP POLICY IF EXISTS user_organization_access ON {_command.Schema}.user_organizations;", $"DROP POLICY IF EXISTS account_organization_access ON {_command.Schema}.account_organizations;",
@$"CREATE POLICY user_organization_access ON {_command.Schema}.user_organizations @$"CREATE POLICY account_organization_access ON {_command.Schema}.account_organizations
USING (user_id = current_setting('app.user_id', TRUE)::INTEGER) WITH CHECK (true);" USING (account_id = current_setting('app.account_id', TRUE)::INTEGER) WITH CHECK (true);"
}; };
foreach (var statement in sql) foreach (var statement in sql)

View file

@ -1,9 +1,9 @@
using System.Diagnostics; using System.Diagnostics;
using Microsoft.ApplicationInsights; using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.DataContracts; using Microsoft.ApplicationInsights.DataContracts;
using PlanTempus.Core.CommandQueries; using PlanTempus.Core.CommandQueries;
namespace PlanTempus.Components.Users.Create; namespace PlanTempus.Components.Accounts.Create;
public class CommandHandlerDecorator<TCommand>( public class CommandHandlerDecorator<TCommand>(
ICommandHandler<TCommand> decoratedHandler, ICommandHandler<TCommand> decoratedHandler,

View file

@ -1,8 +1,8 @@
using PlanTempus.Core.CommandQueries; using PlanTempus.Core.CommandQueries;
namespace PlanTempus.Components.Users.Create namespace PlanTempus.Components.Accounts.Create
{ {
public class CreateUserCommand : Command public class CreateAccountCommand : Command
{ {
public required string Email { get; set; } public required string Email { get; set; }
public required string Password { get; set; } public required string Password { get; set; }

View file

@ -1,14 +1,14 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using PlanTempus.Core.CommandQueries; using PlanTempus.Core.CommandQueries;
namespace PlanTempus.Components.Users.Create namespace PlanTempus.Components.Accounts.Create
{ {
[ApiController] [ApiController]
[Route("api/users")] [Route("api/accounts")]
public class CreateUserController(CreateUserHandler handler, CreateUserValidator validator) : ControllerBase public class CreateAccountController(CreateAccountHandler handler, CreateAccountValidator validator) : ControllerBase
{ {
[HttpPost] [HttpPost]
public async Task<ActionResult<CommandResponse>> Create([FromBody] CreateUserCommand command) public async Task<ActionResult<CommandResponse>> Create([FromBody] CreateAccountCommand command)
{ {
try try
{ {

View file

@ -1,24 +1,24 @@
using Insight.Database; using Insight.Database;
using Microsoft.ApplicationInsights; using Microsoft.ApplicationInsights;
using Npgsql; using Npgsql;
using PlanTempus.Components.Users.Exceptions; using PlanTempus.Components.Accounts.Exceptions;
using PlanTempus.Core; using PlanTempus.Core;
using PlanTempus.Core.CommandQueries; using PlanTempus.Core.CommandQueries;
using PlanTempus.Core.Database; using PlanTempus.Core.Database;
namespace PlanTempus.Components.Users.Create namespace PlanTempus.Components.Accounts.Create
{ {
public class CreateUserHandler( public class CreateAccountHandler(
IDatabaseOperations databaseOperations, IDatabaseOperations databaseOperations,
ISecureTokenizer secureTokenizer) : ICommandHandler<CreateUserCommand> ISecureTokenizer secureTokenizer) : ICommandHandler<CreateAccountCommand>
{ {
public async Task<CommandResponse> Handle(CreateUserCommand command) public async Task<CommandResponse> Handle(CreateAccountCommand command)
{ {
using var db = databaseOperations.CreateScope(nameof(CreateUserHandler)); using var db = databaseOperations.CreateScope(nameof(CreateAccountHandler));
try try
{ {
var sql = @" var sql = @"
INSERT INTO system.users(email , password_hash, security_stamp, email_confirmed, INSERT INTO system.accounts(email , password_hash, security_stamp, email_confirmed,
access_failed_count, lockout_enabled, access_failed_count, lockout_enabled,
is_active) is_active)
VALUES(@Email, @PasswordHash, @SecurityStamp, @EmailConfirmed, VALUES(@Email, @PasswordHash, @SecurityStamp, @EmailConfirmed,
@ -35,11 +35,10 @@ namespace PlanTempus.Components.Users.Create
LockoutEnabled = false, LockoutEnabled = false,
command.IsActive, command.IsActive,
}); });
//lav en mapping mellem requestid og userid
return new CommandResponse(command.CorrelationId, command.GetType().Name, command.TransactionId); return new CommandResponse(command.CorrelationId, command.GetType().Name, command.TransactionId);
} }
catch (PostgresException ex) when (ex.SqlState == "23505" && ex.ConstraintName.Equals("users_email_key", StringComparison.InvariantCultureIgnoreCase)) catch (PostgresException ex) when (ex.SqlState == "23505" && ex.ConstraintName.Equals("accounts_email_key", StringComparison.InvariantCultureIgnoreCase))
{ {
db.Error(ex); db.Error(ex);
throw new EmailAlreadyRegistreredException(); throw new EmailAlreadyRegistreredException();

View file

@ -1,7 +1,7 @@
namespace PlanTempus.Components.Users.Create namespace PlanTempus.Components.Accounts.Create
{ {
public class CreateUserResult public class CreateAccountResult
{ {
public long Id { get; set; } public long Id { get; set; }
public string Email { get; set; } public string Email { get; set; }

View file

@ -1,10 +1,10 @@
using FluentValidation; using FluentValidation;
namespace PlanTempus.Components.Users.Create namespace PlanTempus.Components.Accounts.Create
{ {
public class CreateUserValidator : AbstractValidator<CreateUserCommand> public class CreateAccountValidator : AbstractValidator<CreateAccountCommand>
{ {
public CreateUserValidator() public CreateAccountValidator()
{ {
RuleFor(x => x.Email) RuleFor(x => x.Email)
.NotEmpty().WithMessage("Email skal angives.") .NotEmpty().WithMessage("Email skal angives.")

View file

@ -1,4 +1,4 @@
namespace PlanTempus.Components.Users.Exceptions; namespace PlanTempus.Components.Accounts.Exceptions;
public class EmailAlreadyRegistreredException: Exception public class EmailAlreadyRegistreredException: Exception
{ {

View file

@ -1,5 +1,4 @@
using Autofac; using Autofac;
using PlanTempus.Components.Users.Create;
using PlanTempus.Core.CommandQueries; using PlanTempus.Core.CommandQueries;
namespace PlanTempus.Components; namespace PlanTempus.Components;

View file

@ -1,5 +1,5 @@
using Autofac; using Autofac;
using PlanTempus.Components.Users.Create; using PlanTempus.Components.Accounts.Create;
using PlanTempus.Core.CommandQueries; using PlanTempus.Core.CommandQueries;
using PlanTempus.Core.SeqLogging; using PlanTempus.Core.SeqLogging;

View file

@ -3,6 +3,6 @@
public class CreateOrganizationCommand public class CreateOrganizationCommand
{ {
public string ConnectionString { get; set; } public string ConnectionString { get; set; }
public Guid UserId { get; set; } public Guid AccountId { get; set; }
} }
} }

View file

@ -26,9 +26,9 @@ namespace PlanTempus.Components.Organizations.Create
var orgResult = data.First(); var orgResult = data.First();
await db.Connection.ExecuteAsync(@$" await db.Connection.ExecuteAsync(@$"
INSERT INTO user_organizations (user_id, organization_id) INSERT INTO account_organizations (account_id, organization_id)
VALUES (@UserId, @OrganizationId)", VALUES (@AccountId, @OrganizationId)",
new { command.UserId, OrganizationId = orgResult.Id }); new { command.AccountId, OrganizationId = orgResult.Id });
transaction.Commit(); transaction.Commit();

View file

@ -1,4 +1,4 @@
// PlanTempus.X.Services.cs // PlanTempus.X.Services.cs
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -6,11 +6,10 @@ using System.Threading.Tasks;
namespace PlanTempus.X.Services namespace PlanTempus.X.Services
{ {
// Models // Models
public class User public class Account
{ {
public string Id { get; set; } public string Id { get; set; }
public string Email { get; set; } public string Email { get; set; }
public string Name { get; set; }
public bool EmailConfirmed { get; set; } public bool EmailConfirmed { get; set; }
public string Password { get; set; } public string Password { get; set; }
public bool IsLocked { get; set; } public bool IsLocked { get; set; }
@ -24,19 +23,19 @@ namespace PlanTempus.X.Services
public string CreatedBy { get; set; } public string CreatedBy { get; set; }
} }
public class UserOrganization public class AccountOrganization
{ {
public string UserId { get; set; } public string AccountId { get; set; }
public string OrganizationId { get; set; } public string OrganizationId { get; set; }
public string Role { get; set; } public string Role { get; set; }
} }
// Service interfaces // Service interfaces
public interface IUserService public interface IAccountService
{ {
Task<User> CreateUserAsync(string email, string name); Task<Account> CreateAccountAsync(string email, string password);
Task<User> GetUserByEmailAsync(string email); Task<Account> GetAccountByEmailAsync(string email);
User GetUserByEmail(string email); Account GetAccountByEmail(string email);
Task ConfirmEmailAsync(string confirmationToken); Task ConfirmEmailAsync(string confirmationToken);
} }
@ -48,14 +47,14 @@ namespace PlanTempus.X.Services
public interface IOrganizationService public interface IOrganizationService
{ {
Task<Organization> SetupOrganizationAsync(string userId, string orgName, string password); Task<Organization> SetupOrganizationAsync(string accountId, string orgName, string password);
Task<Organization> CreateOrganizationAsync(string userId, string orgName); Task<Organization> CreateOrganizationAsync(string accountId, string orgName);
} }
public interface IUserOrganizationService public interface IAccountOrganizationService
{ {
UserOrganization GetUserOrganization(string userId, string organizationId); AccountOrganization GetAccountOrganization(string accountId, string organizationId);
List<UserOrganization> GetUserOrganizations(string userId); List<AccountOrganization> GetAccountOrganizations(string accountId);
} }
public interface ITenantService public interface ITenantService
@ -65,26 +64,26 @@ namespace PlanTempus.X.Services
public interface IAuthService public interface IAuthService
{ {
bool IsUserAuthenticated(string userId); bool IsAccountAuthenticated(string accountId);
Task<bool> AttemptLoginAsync(string email, string password); Task<bool> AttemptLoginAsync(string email, string password);
} }
// Service implementations // Service implementations
public class UserService : IUserService public class AccountService : IAccountService
{ {
public async Task<User> CreateUserAsync(string email, string name) public async Task<Account> CreateAccountAsync(string email, string password)
{ {
throw new NotImplementedException("CreateUserAsync not implemented"); throw new NotImplementedException("CreateAccountAsync not implemented");
} }
public async Task<User> GetUserByEmailAsync(string email) public async Task<Account> GetAccountByEmailAsync(string email)
{ {
throw new NotImplementedException("GetUserByEmailAsync not implemented"); throw new NotImplementedException("GetAccountByEmailAsync not implemented");
} }
public User GetUserByEmail(string email) public Account GetAccountByEmail(string email)
{ {
throw new NotImplementedException("GetUserByEmail not implemented"); throw new NotImplementedException("GetAccountByEmail not implemented");
} }
public async Task ConfirmEmailAsync(string confirmationToken) public async Task ConfirmEmailAsync(string confirmationToken)
@ -108,27 +107,27 @@ namespace PlanTempus.X.Services
public class OrganizationService : IOrganizationService public class OrganizationService : IOrganizationService
{ {
public async Task<Organization> SetupOrganizationAsync(string userId, string orgName, string password) public async Task<Organization> SetupOrganizationAsync(string accountId, string orgName, string password)
{ {
throw new NotImplementedException("SetupOrganizationAsync not implemented"); throw new NotImplementedException("SetupOrganizationAsync not implemented");
} }
public async Task<Organization> CreateOrganizationAsync(string userId, string orgName) public async Task<Organization> CreateOrganizationAsync(string accountId, string orgName)
{ {
throw new NotImplementedException("CreateOrganizationAsync not implemented"); throw new NotImplementedException("CreateOrganizationAsync not implemented");
} }
} }
public class UserOrganizationService : IUserOrganizationService public class AccountOrganizationService : IAccountOrganizationService
{ {
public UserOrganization GetUserOrganization(string userId, string organizationId) public AccountOrganization GetAccountOrganization(string accountId, string organizationId)
{ {
throw new NotImplementedException("GetUserOrganization not implemented"); throw new NotImplementedException("GetAccountOrganization not implemented");
} }
public List<UserOrganization> GetUserOrganizations(string userId) public List<AccountOrganization> GetAccountOrganizations(string accountId)
{ {
throw new NotImplementedException("GetUserOrganizations not implemented"); throw new NotImplementedException("GetAccountOrganizations not implemented");
} }
} }
@ -142,9 +141,9 @@ namespace PlanTempus.X.Services
public class AuthService : IAuthService public class AuthService : IAuthService
{ {
public bool IsUserAuthenticated(string userId) public bool IsAccountAuthenticated(string accountId)
{ {
throw new NotImplementedException("IsUserAuthenticated not implemented"); throw new NotImplementedException("IsAccountAuthenticated not implemented");
} }
public async Task<bool> AttemptLoginAsync(string email, string password) public async Task<bool> AttemptLoginAsync(string email, string password)

View file

@ -1,4 +1,4 @@
using LightBDD.Framework; using LightBDD.Framework;
using LightBDD.Framework.Scenarios; using LightBDD.Framework.Scenarios;
using LightBDD.MsTest3; using LightBDD.MsTest3;
using PlanTempus.X.Services; using PlanTempus.X.Services;
@ -9,29 +9,29 @@ namespace PlanTempus.X.BDD.FeatureFixtures;
[FeatureDescription(@"As a new user [FeatureDescription(@"As a new user
I want to register with my email I want to register with my email
So I can start using the system")] So I can start using the system")]
public partial class UserRegistrationSpecs : FeatureFixture public partial class AccountRegistrationSpecs : FeatureFixture
{ {
protected User _currentUser; protected Account _currentAccount;
protected string _currentEmail; protected string _currentEmail;
protected Exception _registrationError; protected Exception _registrationError;
IUserService _userService; IAccountService _accountService;
IEmailService _emailService; IEmailService _emailService;
IOrganizationService _organizationService; IOrganizationService _organizationService;
public async Task Given_no_user_exists_with_email(string email) public async Task Given_no_account_exists_with_email(string email)
{ {
// Ensure user doesn't exist with email // Ensure account doesn't exist with email
var user = await _userService.GetUserByEmailAsync(email); var account = await _accountService.GetAccountByEmailAsync(email);
user.ShouldBeNull(); account.ShouldBeNull();
_currentEmail = email; _currentEmail = email;
} }
public async Task When_I_submit_registration_with_name_and_email(string name, string email) public async Task When_I_submit_registration_with_email_and_password(string email, string password)
{ {
try try
{ {
_currentUser = await _userService.CreateUserAsync(email, name); _currentAccount = await _accountService.CreateAccountAsync(email, password);
_currentEmail = email; _currentEmail = email;
} }
catch (Exception ex) catch (Exception ex)
@ -44,7 +44,7 @@ public partial class UserRegistrationSpecs : FeatureFixture
{ {
try try
{ {
_currentUser = await _userService.CreateUserAsync(email, "Test User"); _currentAccount = await _accountService.CreateAccountAsync(email, "TestPassword123!");
_currentEmail = email; _currentEmail = email;
} }
catch (Exception ex) catch (Exception ex)
@ -53,14 +53,13 @@ public partial class UserRegistrationSpecs : FeatureFixture
} }
} }
public async Task Then_a_new_user_should_be_created_with_email_and_confirmation_status(string email, bool confirmationStatus) public async Task Then_a_new_account_should_be_created_with_email_and_confirmation_status(string email, bool confirmationStatus)
{ {
_currentUser.ShouldNotBeNull(); _currentAccount.ShouldNotBeNull();
_currentUser.Email.ShouldBe(email); _currentAccount.Email.ShouldBe(email);
_currentUser.EmailConfirmed.ShouldBe(confirmationStatus); _currentAccount.EmailConfirmed.ShouldBe(confirmationStatus);
await Task.CompletedTask; await Task.CompletedTask;
} }
public async Task Then_a_confirmation_email_should_be_sent() public async Task Then_a_confirmation_email_should_be_sent()
@ -69,18 +68,16 @@ public partial class UserRegistrationSpecs : FeatureFixture
emailSent.ShouldBeTrue(); emailSent.ShouldBeTrue();
await Task.CompletedTask; await Task.CompletedTask;
} }
public async Task Given_a_user_already_exists_with_email(string email) public async Task Given_an_account_already_exists_with_email(string email)
{ {
// Create a user first to ensure it exists // Create an account first to ensure it exists
_currentUser = await _userService.CreateUserAsync(email, "Existing User"); _currentAccount = await _accountService.CreateAccountAsync(email, "ExistingPassword123!");
_currentUser.ShouldNotBeNull(); _currentAccount.ShouldNotBeNull();
_currentEmail = email; _currentEmail = email;
await Task.CompletedTask; await Task.CompletedTask;
} }
public async Task Then_registration_should_fail_with_error(string expectedErrorMessage) public async Task Then_registration_should_fail_with_error(string expectedErrorMessage)
@ -89,6 +86,5 @@ public partial class UserRegistrationSpecs : FeatureFixture
_registrationError.Message.ShouldBe(expectedErrorMessage); _registrationError.Message.ShouldBe(expectedErrorMessage);
await Task.CompletedTask; await Task.CompletedTask;
} }
} }

View file

@ -5,28 +5,29 @@ using PlanTempus.X.Services;
using Shouldly; using Shouldly;
namespace PlanTempus.X.BDD.FeatureFixtures; namespace PlanTempus.X.BDD.FeatureFixtures;
[TestClass] [TestClass]
[FeatureDescription(@"As a system administrator [FeatureDescription(@"As a system administrator
I want to ensure account security is maintained I want to ensure account security is maintained
So users' data remains protected")] So users' data remains protected")]
public partial class AccountSecuritySpecs : FeatureFixture public partial class AccountSecuritySpecs : FeatureFixture
{ {
IUserService _userService; IAccountService _accountService;
IEmailService _emailService; IEmailService _emailService;
IOrganizationService _organizationService; IOrganizationService _organizationService;
IAuthService _authService; IAuthService _authService;
protected User _currentUser; protected Account _currentAccount;
protected DateTime? _lockoutEnd; protected DateTime? _lockoutEnd;
protected bool _isLocked; protected bool _isLocked;
protected bool _loginSuccessful; protected bool _loginSuccessful;
public async Task Given_user_exists(string email) public async Task Given_account_exists(string email)
{ {
_currentUser = await _userService.GetUserByEmailAsync(email); _currentAccount = await _accountService.GetAccountByEmailAsync(email);
if (_currentUser == null) if (_currentAccount == null)
{ {
_currentUser = await _userService.CreateUserAsync(email, "Test User"); _currentAccount = await _accountService.CreateAccountAsync(email, "TestPassword123!");
} }
} }
@ -36,7 +37,7 @@ public partial class AccountSecuritySpecs : FeatureFixture
{ {
try try
{ {
await _authService.AttemptLoginAsync(_currentUser.Email, "WrongPassword"); await _authService.AttemptLoginAsync(_currentAccount.Email, "WrongPassword");
} }
catch catch
{ {
@ -47,29 +48,27 @@ public partial class AccountSecuritySpecs : FeatureFixture
public async Task Then_the_account_should_be_locked() public async Task Then_the_account_should_be_locked()
{ {
_currentUser = _userService.GetUserByEmail(_currentUser.Email); _currentAccount = _accountService.GetAccountByEmail(_currentAccount.Email);
_isLocked = _currentUser.IsLocked; _isLocked = _currentAccount.IsLocked;
_isLocked.ShouldBeTrue(); _isLocked.ShouldBeTrue();
await Task.CompletedTask; await Task.CompletedTask;
} }
public async Task And_lockout_end_should_be_set() public async Task And_lockout_end_should_be_set()
{ {
_lockoutEnd = _currentUser.LockoutEnd; _lockoutEnd = _currentAccount.LockoutEnd;
_lockoutEnd.ShouldNotBeNull(); _lockoutEnd.ShouldNotBeNull();
_lockoutEnd.Value.ShouldBeGreaterThan(DateTime.UtcNow); _lockoutEnd.Value.ShouldBeGreaterThan(DateTime.UtcNow);
await Task.CompletedTask; await Task.CompletedTask;
} }
public async Task And_subsequent_login_attempts_should_fail_until_lockout_end() public async Task And_subsequent_login_attempts_should_fail_until_lockout_end()
{ {
try try
{ {
_loginSuccessful = await _authService.AttemptLoginAsync(_currentUser.Email, _currentUser.Password); _loginSuccessful = await _authService.AttemptLoginAsync(_currentAccount.Email, _currentAccount.Password);
} }
catch catch
{ {

View file

@ -5,40 +5,41 @@ using PlanTempus.X.Services;
using Shouldly; using Shouldly;
namespace PlanTempus.X.BDD.FeatureFixtures; namespace PlanTempus.X.BDD.FeatureFixtures;
[TestClass] [TestClass]
[FeatureDescription(@"As a registered user [FeatureDescription(@"As a registered user
I want to confirm my email I want to confirm my email
So I can activate my account")] So I can activate my account")]
public partial class EmailConfirmationSpecs : FeatureFixture public partial class EmailConfirmationSpecs : FeatureFixture
{ {
IUserService _userService; IAccountService _accountService;
IEmailService _emailService; IEmailService _emailService;
IOrganizationService _organizationService; IOrganizationService _organizationService;
protected User _currentUser; protected Account _currentAccount;
protected string _currentEmail; protected string _currentEmail;
protected string _confirmationLink; protected string _confirmationLink;
protected bool _redirectedToWelcome; protected bool _redirectedToWelcome;
protected string _errorMessage; protected string _errorMessage;
public async Task Given_a_user_exists_with_unconfirmed_email(string email) public async Task Given_an_account_exists_with_unconfirmed_email(string email)
{ {
_currentUser = await _userService.CreateUserAsync(email, "Test User"); _currentAccount = await _accountService.CreateAccountAsync(email, "TestPassword123!");
_currentUser.EmailConfirmed.ShouldBeFalse(); _currentAccount.EmailConfirmed.ShouldBeFalse();
_currentEmail = email; _currentEmail = email;
} }
public async Task When_I_click_the_valid_confirmation_link_for(string email) public async Task When_I_click_the_valid_confirmation_link_for(string email)
{ {
_confirmationLink = await _emailService.GetConfirmationLinkForEmail(email); _confirmationLink = await _emailService.GetConfirmationLinkForEmail(email);
await _userService.ConfirmEmailAsync(_confirmationLink); await _accountService.ConfirmEmailAsync(_confirmationLink);
_redirectedToWelcome = true; // Simulate redirect _redirectedToWelcome = true; // Simulate redirect
} }
public async Task Then_the_users_email_confirmed_should_be_true() public async Task Then_the_accounts_email_confirmed_should_be_true()
{ {
_currentUser = _userService.GetUserByEmail(_currentEmail); _currentAccount = _accountService.GetAccountByEmail(_currentEmail);
_currentUser.EmailConfirmed.ShouldBeTrue(); _currentAccount.EmailConfirmed.ShouldBeTrue();
} }
public async Task And_I_should_be_redirected_to_the_welcome_page() public async Task And_I_should_be_redirected_to_the_welcome_page()
@ -50,7 +51,7 @@ public partial class EmailConfirmationSpecs : FeatureFixture
{ {
try try
{ {
await _userService.ConfirmEmailAsync("invalid-confirmation-token"); await _accountService.ConfirmEmailAsync("invalid-confirmation-token");
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -65,9 +66,9 @@ public partial class EmailConfirmationSpecs : FeatureFixture
public async Task And_my_email_remains_unconfirmed() public async Task And_my_email_remains_unconfirmed()
{ {
if (_currentUser != null) if (_currentAccount != null)
{ {
_currentUser.EmailConfirmed.ShouldBeFalse(); _currentAccount.EmailConfirmed.ShouldBeFalse();
} }
} }
} }

View file

@ -5,38 +5,39 @@ using PlanTempus.X.Services;
using Shouldly; using Shouldly;
namespace PlanTempus.X.BDD.FeatureFixtures; namespace PlanTempus.X.BDD.FeatureFixtures;
[TestClass] [TestClass]
[FeatureDescription(@"As a user with confirmed email [FeatureDescription(@"As a user with confirmed email
I want to set up my organization I want to set up my organization
So I can start using the system with my team")] So I can start using the system with my team")]
public partial class OrganizationSetupSpecs : FeatureFixture public partial class OrganizationSetupSpecs : FeatureFixture
{ {
IUserService _userService; IAccountService _accountService;
IEmailService _emailService; IEmailService _emailService;
IOrganizationService _organizationService; IOrganizationService _organizationService;
IUserOrganizationService _userOrganizationService; IAccountOrganizationService _accountOrganizationService;
ITenantService _tenantService; ITenantService _tenantService;
IAuthService _authService; IAuthService _authService;
protected User _currentUser; protected Account _currentAccount;
protected Organization _organization; protected Organization _organization;
protected Exception _setupError; protected Exception _setupError;
protected List<Organization> _userOrganizations; protected List<Organization> _accountOrganizations;
public async Task Given_user_has_confirmed_their_email(string email) public async Task Given_account_has_confirmed_their_email(string email)
{ {
// Create a user with confirmed email // Create an account with confirmed email
_currentUser = await _userService.CreateUserAsync(email, "Test User"); _currentAccount = await _accountService.CreateAccountAsync(email, "TestPassword123!");
var confirmationLink = await _emailService.GetConfirmationLinkForEmail(email); var confirmationLink = await _emailService.GetConfirmationLinkForEmail(email);
await _userService.ConfirmEmailAsync(confirmationLink); await _accountService.ConfirmEmailAsync(confirmationLink);
_currentUser.EmailConfirmed.ShouldBeTrue(); _currentAccount.EmailConfirmed.ShouldBeTrue();
} }
public async Task When_user_submit_organization_name_and_valid_password(string orgName, string password) public async Task When_account_submit_organization_name_and_valid_password(string orgName, string password)
{ {
try try
{ {
_organization = await _organizationService.SetupOrganizationAsync(_currentUser.Id, orgName, password); _organization = await _organizationService.SetupOrganizationAsync(_currentAccount.Id, orgName, password);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -48,18 +49,17 @@ public partial class OrganizationSetupSpecs : FeatureFixture
{ {
_organization.ShouldNotBeNull(); _organization.ShouldNotBeNull();
_organization.Name.ShouldBe("Acme Corp"); _organization.Name.ShouldBe("Acme Corp");
_organization.CreatedBy.ShouldBe(_currentUser.Id); _organization.CreatedBy.ShouldBe(_currentAccount.Id);
await Task.CompletedTask; await Task.CompletedTask;
} }
public async Task And_the_user_should_be_linked_to_the_organization_in_user_organizations() public async Task And_the_account_should_be_linked_to_the_organization_in_account_organizations()
{ {
var userOrg = _userOrganizationService.GetUserOrganization(_currentUser.Id, _organization.Id); var accountOrg = _accountOrganizationService.GetAccountOrganization(_currentAccount.Id, _organization.Id);
userOrg.ShouldNotBeNull(); accountOrg.ShouldNotBeNull();
await Task.CompletedTask; await Task.CompletedTask;
} }
public async Task And_tenant_tables_should_be_created_for_the_organization() public async Task And_tenant_tables_should_be_created_for_the_organization()
@ -68,23 +68,21 @@ public partial class OrganizationSetupSpecs : FeatureFixture
tenantTablesExist.ShouldBeTrue(); tenantTablesExist.ShouldBeTrue();
await Task.CompletedTask; await Task.CompletedTask;
} }
public async Task And_user_should_be_logged_into_the_system() public async Task And_account_should_be_logged_into_the_system()
{ {
var isAuthenticated = _authService.IsUserAuthenticated(_currentUser.Id); var isAuthenticated = _authService.IsAccountAuthenticated(_currentAccount.Id);
isAuthenticated.ShouldBeTrue(); isAuthenticated.ShouldBeTrue();
await Task.CompletedTask; await Task.CompletedTask;
} }
public async Task When_user_submit_organization_name_without_password(string orgName) public async Task When_account_submit_organization_name_without_password(string orgName)
{ {
try try
{ {
await _organizationService.SetupOrganizationAsync(_currentUser.Id, orgName, ""); await _organizationService.SetupOrganizationAsync(_currentAccount.Id, orgName, "");
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -98,37 +96,34 @@ public partial class OrganizationSetupSpecs : FeatureFixture
_setupError.Message.ShouldBe(expectedErrorMessage); _setupError.Message.ShouldBe(expectedErrorMessage);
await Task.CompletedTask; await Task.CompletedTask;
} }
public async Task Given_user_has_completed_initial_setup(string email) public async Task Given_account_has_completed_initial_setup(string email)
{ {
await Given_user_has_confirmed_their_email(email); await Given_account_has_confirmed_their_email(email);
await When_user_submit_organization_name_and_valid_password("First Org", "ValidP@ssw0rd"); await When_account_submit_organization_name_and_valid_password("First Org", "ValidP@ssw0rd");
_userOrganizations = new List<Organization> { _organization }; _accountOrganizations = new List<Organization> { _organization };
} }
public async Task When_user_create_a_new_organization(string orgName) public async Task When_account_create_a_new_organization(string orgName)
{ {
var newOrg = await _organizationService.CreateOrganizationAsync(_currentUser.Id, orgName); var newOrg = await _organizationService.CreateOrganizationAsync(_currentAccount.Id, orgName);
_userOrganizations.Add(newOrg); _accountOrganizations.Add(newOrg);
} }
public async Task Then_a_new_organization_entry_should_be_created() public async Task Then_a_new_organization_entry_should_be_created()
{ {
_userOrganizations.Count.ShouldBe(2); _accountOrganizations.Count.ShouldBe(2);
_userOrganizations[1].Name.ShouldBe("Second Org"); _accountOrganizations[1].Name.ShouldBe("Second Org");
await Task.CompletedTask; await Task.CompletedTask;
} }
public async Task And_the_user_should_be_linked_to_both_organizations() public async Task And_the_account_should_be_linked_to_both_organizations()
{ {
var userOrgs = _userOrganizationService.GetUserOrganizations(_currentUser.Id); var accountOrgs = _accountOrganizationService.GetAccountOrganizations(_currentAccount.Id);
userOrgs.Count.ShouldBe(2); accountOrgs.Count.ShouldBe(2);
await Task.CompletedTask; await Task.CompletedTask;
} }
} }

View file

@ -0,0 +1,31 @@
using LightBDD.Framework;
using LightBDD.Framework.Scenarios;
using LightBDD.MsTest3;
namespace PlanTempus.X.BDD.Scenarios;
[TestClass]
public partial class AccountRegistrationSpecs : FeatureFixtures.AccountRegistrationSpecs
{
[Scenario]
[TestMethod]
public async Task Successful_account_registration_with_valid_email()
{
await Runner.RunScenarioAsync(
_ => Given_no_account_exists_with_email("test@example.com"),
_ => When_I_submit_registration_with_email_and_password("test@example.com", "TestPassword123!"),
_ => Then_a_new_account_should_be_created_with_email_and_confirmation_status("test@example.com", false),
_ => Then_a_confirmation_email_should_be_sent()
);
}
[Scenario]
[TestMethod]
public async Task Reject_duplicate_email_registration()
{
await Runner.RunScenarioAsync(
_ => Given_an_account_already_exists_with_email("existing@example.com"),
_ => When_I_submit_registration_with_email("existing@example.com"),
_ => Then_registration_should_fail_with_error("Email already exists")
);
}
}

View file

@ -3,15 +3,16 @@ using LightBDD.Framework.Scenarios;
using LightBDD.MsTest3; using LightBDD.MsTest3;
namespace PlanTempus.X.BDD.Scenarios; namespace PlanTempus.X.BDD.Scenarios;
[TestClass] [TestClass]
public partial class AccountSecuritySpecs : FeatureFixtures.AccountSecuritySpecs public partial class AccountSecuritySpecs : FeatureFixtures.AccountSecuritySpecs
{ {
[Scenario] [Scenario]
[TestMethod] [TestMethod]
public async Task User_lockout_after_multiple_failed_attempts() public async Task Account_lockout_after_multiple_failed_attempts()
{ {
await Runner.RunScenarioAsync( await Runner.RunScenarioAsync(
_ => Given_user_exists("test@example.com"), _ => Given_account_exists("test@example.com"),
_ => When_I_attempt_5_failed_logins_within_5_minutes(), _ => When_I_attempt_5_failed_logins_within_5_minutes(),
_ => Then_the_account_should_be_locked(), _ => Then_the_account_should_be_locked(),
_ => And_lockout_end_should_be_set(), _ => And_lockout_end_should_be_set(),

View file

@ -3,6 +3,7 @@ using LightBDD.Framework.Scenarios;
using LightBDD.MsTest3; using LightBDD.MsTest3;
namespace PlanTempus.X.BDD.Scenarios; namespace PlanTempus.X.BDD.Scenarios;
[TestClass] [TestClass]
public partial class EmailConfirmationSpecs : FeatureFixtures.EmailConfirmationSpecs public partial class EmailConfirmationSpecs : FeatureFixtures.EmailConfirmationSpecs
{ {
@ -11,9 +12,9 @@ public partial class EmailConfirmationSpecs : FeatureFixtures.EmailConfirmationS
public async Task Confirm_valid_email_address() public async Task Confirm_valid_email_address()
{ {
await Runner.RunScenarioAsync( await Runner.RunScenarioAsync(
_ => Given_a_user_exists_with_unconfirmed_email("test@example.com"), _ => Given_an_account_exists_with_unconfirmed_email("test@example.com"),
_ => When_I_click_the_valid_confirmation_link_for("test@example.com"), _ => When_I_click_the_valid_confirmation_link_for("test@example.com"),
_ => Then_the_users_email_confirmed_should_be_true(), _ => Then_the_accounts_email_confirmed_should_be_true(),
_ => And_I_should_be_redirected_to_the_welcome_page() _ => And_I_should_be_redirected_to_the_welcome_page()
); );
} }

View file

@ -3,6 +3,7 @@ using LightBDD.Framework.Scenarios;
using LightBDD.MsTest3; using LightBDD.MsTest3;
namespace PlanTempus.X.BDD.Scenarios; namespace PlanTempus.X.BDD.Scenarios;
[TestClass] [TestClass]
public partial class OrganizationSetupSpecs : FeatureFixtures.OrganizationSetupSpecs public partial class OrganizationSetupSpecs : FeatureFixtures.OrganizationSetupSpecs
{ {
@ -11,12 +12,12 @@ public partial class OrganizationSetupSpecs : FeatureFixtures.OrganizationSetupS
public async Task Complete_organization_setup_after_confirmation() public async Task Complete_organization_setup_after_confirmation()
{ {
await Runner.RunScenarioAsync( await Runner.RunScenarioAsync(
_ => Given_user_has_confirmed_their_email("test@example.com"), _ => Given_account_has_confirmed_their_email("test@example.com"),
_ => When_user_submit_organization_name_and_valid_password("Acme Corp", "ValidP@ssw0rd"), _ => When_account_submit_organization_name_and_valid_password("Acme Corp", "ValidP@ssw0rd"),
_ => Then_a_new_organization_should_be_created_with_expected_properties(), _ => Then_a_new_organization_should_be_created_with_expected_properties(),
_ => And_the_user_should_be_linked_to_the_organization_in_user_organizations(), _ => And_the_account_should_be_linked_to_the_organization_in_account_organizations(),
_ => And_tenant_tables_should_be_created_for_the_organization(), _ => And_tenant_tables_should_be_created_for_the_organization(),
_ => And_user_should_be_logged_into_the_system() _ => And_account_should_be_logged_into_the_system()
); );
} }
@ -25,8 +26,8 @@ public partial class OrganizationSetupSpecs : FeatureFixtures.OrganizationSetupS
public async Task Prevent_organization_setup_without_password() public async Task Prevent_organization_setup_without_password()
{ {
await Runner.RunScenarioAsync( await Runner.RunScenarioAsync(
_ => Given_user_has_confirmed_their_email("test@example.com"), _ => Given_account_has_confirmed_their_email("test@example.com"),
_ => When_user_submit_organization_name_without_password("Acme Corp"), _ => When_account_submit_organization_name_without_password("Acme Corp"),
_ => Then_organization_setup_should_fail_with_error("Password required") _ => Then_organization_setup_should_fail_with_error("Password required")
); );
} }
@ -36,10 +37,10 @@ public partial class OrganizationSetupSpecs : FeatureFixtures.OrganizationSetupS
public async Task Handle_multiple_organization_creations() public async Task Handle_multiple_organization_creations()
{ {
await Runner.RunScenarioAsync( await Runner.RunScenarioAsync(
_ => Given_user_has_completed_initial_setup("test@example.com"), _ => Given_account_has_completed_initial_setup("test@example.com"),
_ => When_user_create_a_new_organization("Second Org"), _ => When_account_create_a_new_organization("Second Org"),
_ => Then_a_new_organization_entry_should_be_created(), _ => Then_a_new_organization_entry_should_be_created(),
_ => And_the_user_should_be_linked_to_both_organizations() _ => And_the_account_should_be_linked_to_both_organizations()
); );
} }
} }

View file

@ -1,31 +0,0 @@
using LightBDD.Framework;
using LightBDD.Framework.Scenarios;
using LightBDD.MsTest3;
namespace PlanTempus.X.BDD.Scenarios;
[TestClass]
public partial class UserRegistrationSpecs : FeatureFixtures.UserRegistrationSpecs
{
[Scenario]
[TestMethod]
public async Task Successful_user_registration_with_valid_email()
{
await Runner.RunScenarioAsync(
_ => Given_no_user_exists_with_email("test@example.com"),
_ => When_I_submit_registration_with_name_and_email("Test User", "test@example.com"),
_ => Then_a_new_user_should_be_created_with_email_and_confirmation_status("test@example.com", false),
_ => Then_a_confirmation_email_should_be_sent()
);
}
[Scenario]
[TestMethod]
public async Task Reject_duplicate_email_registration()
{
await Runner.RunScenarioAsync(
_ => Given_a_user_already_exists_with_email("existing@example.com"),
_ => When_I_submit_registration_with_email("existing@example.com"),
_ => Then_registration_should_fail_with_error("Email already exists")
);
}
}

View file

@ -1,6 +1,6 @@
{ {
"ConnectionStrings": { "ConnectionStrings": {
"DefaultConnection": "Host=192.168.1.57;Port=5432;Database=ptmain;" "DefaultConnection": "Host=192.168.1.63;Port=5432;Database=ptmain;"
}, },
"ApplicationInsights": { "ApplicationInsights": {
"ConnectionString": "InstrumentationKey=07d2a2b9-5e8e-4924-836e-264f8438f6c5;IngestionEndpoint=https://northeurope-2.in.applicationinsights.azure.com/;LiveEndpoint=https://northeurope.livediagnostics.monitor.azure.com/;ApplicationId=56748c39-2fa3-4880-a1e2-24068e791548", "ConnectionString": "InstrumentationKey=07d2a2b9-5e8e-4924-836e-264f8438f6c5;IngestionEndpoint=https://northeurope-2.in.applicationinsights.azure.com/;LiveEndpoint=https://northeurope.livediagnostics.monitor.azure.com/;ApplicationId=56748c39-2fa3-4880-a1e2-24068e791548",

View file

@ -1,7 +1,7 @@
using Autofac; using Autofac;
using Insight.Database; using Insight.Database;
using PlanTempus.Components; using PlanTempus.Components;
using PlanTempus.Components.Users.Create; using PlanTempus.Components.Accounts.Create;
using PlanTempus.Core.CommandQueries; using PlanTempus.Core.CommandQueries;
using PlanTempus.Core.Database; using PlanTempus.Core.Database;
using PlanTempus.Core.Database.ConnectionFactory; using PlanTempus.Core.Database.ConnectionFactory;
@ -22,7 +22,7 @@ public class HandlerTest : TestFixture
var commandHandler = Container.Resolve<ICommandHandler>(); var commandHandler = Container.Resolve<ICommandHandler>();
commandHandler.ShouldBeOfType<CommandHandler>(); commandHandler.ShouldBeOfType<CommandHandler>();
var command = new CreateUserCommand var command = new CreateAccountCommand
{ {
Email = $"{GetRandomWord()}@dumbanddumber.com5", // Lloyd Christmas Email = $"{GetRandomWord()}@dumbanddumber.com5", // Lloyd Christmas
Password = "1234AceVentura#LOL", // Ace Ventura Password = "1234AceVentura#LOL", // Ace Ventura

View file

@ -1,6 +1,6 @@
using Autofac; using Autofac;
using Insight.Database; using Insight.Database;
using PlanTempus.Components.Users.Exceptions; using PlanTempus.Components.Accounts.Exceptions;
using PlanTempus.Core.Database; using PlanTempus.Core.Database;
using PlanTempus.Core.Database.ConnectionFactory; using PlanTempus.Core.Database.ConnectionFactory;
using Shouldly; using Shouldly;
@ -63,12 +63,12 @@ public class PostgresTests : TestFixture
} }
[TestMethod] [TestMethod]
public async Task TestForUniqueUserEmail() public async Task TestForUniqueAccountEmail()
{ {
using var db = _databaseOperations.CreateScope(nameof(TestForUniqueUserEmail)); using var db = _databaseOperations.CreateScope(nameof(TestForUniqueAccountEmail));
try try
{ {
var sql = @"INSERT INTO system.users(email, password_hash, security_stamp, email_confirmed, access_failed_count, lockout_enabled, is_active) var sql = @"INSERT INTO system.accounts(email, password_hash, security_stamp, email_confirmed, access_failed_count, lockout_enabled, is_active)
VALUES(@Email, @PasswordHash, @SecurityStamp, @EmailConfirmed, @AccessFailedCount, @LockoutEnabled, @IsActive) VALUES(@Email, @PasswordHash, @SecurityStamp, @EmailConfirmed, @AccessFailedCount, @LockoutEnabled, @IsActive)
RETURNING id, created_at, email, is_active"; RETURNING id, created_at, email, is_active";
@ -83,13 +83,13 @@ public class PostgresTests : TestFixture
IsActive = true IsActive = true
}; };
var user = await db.Connection.QuerySqlAsync<dynamic>(sql, parameters); var account = await db.Connection.QuerySqlAsync<dynamic>(sql, parameters);
//EmailAlreadyRegistreredException //EmailAlreadyRegistreredException
//try insert, to test exception //try insert, to test exception
var ex = await Should.ThrowAsync<Npgsql.PostgresException>(async () => var ex = await Should.ThrowAsync<Npgsql.PostgresException>(async () =>
await db.Connection.QuerySqlAsync<dynamic>(sql, parameters)); await db.Connection.QuerySqlAsync<dynamic>(sql, parameters));
ex.ConstraintName.ShouldBe("users_email_key"); ex.ConstraintName.ShouldBe("accounts_email_key");
} }
catch (Exception ex) catch (Exception ex)
{ {