From 88812177a9f04efda6a81f93cae594fdb00e168a Mon Sep 17 00:00:00 2001 From: "Janus C. H. Knudsen" Date: Fri, 9 Jan 2026 22:14:46 +0100 Subject: [PATCH] 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 --- .gitignore | 2 + .vscode/settings.json | 3 + .../{Users/User.cs => Accounts/Account.cs} | 14 ++-- .../{UserService.cs => AccountService.cs} | 36 +++++----- Database/Core/DDL/SetupIdentitySystem.cs | 36 +++++----- .../Create/CommandHandlerDecorator.cs | 54 +++++++------- .../Create/CreateAccountCommand.cs} | 6 +- .../Create/CreateAccountController.cs} | 10 +-- .../Create/CreateAccountHandler.cs} | 23 +++--- .../Create/CreateAccountResult.cs} | 6 +- .../Create/CreateAccountValidator.cs} | 8 +-- .../EmailAlreadyRegistreredException.cs | 6 +- PlanTempus.Components/CommandHandler.cs | 1 - .../ModuleRegistry/CommandModule.cs | 2 +- .../Create/CreateOrganizationCommand.cs | 2 +- .../Create/CreateOrganizationHandler.cs | 6 +- PlanTempus.X.BDD/Class1.cs | 63 ++++++++-------- ...onSpecs.cs => AccountRegistrationSpecs.cs} | 42 +++++------ .../FeatureFixtures/AccountSecuritySpecs.cs | 35 +++++---- .../FeatureFixtures/EmailConfirmationSpecs.cs | 29 ++++---- .../FeatureFixtures/OrganizationSetupSpecs.cs | 71 +++++++++---------- .../Scenarios/AccountRegistrationSpecs.cs | 31 ++++++++ .../Scenarios/AccountSecuritySpecs.cs | 9 +-- .../Scenarios/EmailConfirmationSpecs.cs | 9 +-- .../Scenarios/OrganizationSetupSpecs.cs | 23 +++--- .../Scenarios/UserRegistrationSpecs.cs | 31 -------- SetupInfrastructure/appconfiguration.json | 2 +- Tests/CommandQueryHandlerTests/HandlerTest.cs | 6 +- Tests/PostgresTests.cs | 20 +++--- 29 files changed, 288 insertions(+), 298 deletions(-) create mode 100644 .vscode/settings.json rename Core/Entities/{Users/User.cs => Accounts/Account.cs} (73%) rename Database/Core/{UserService.cs => AccountService.cs} (61%) rename PlanTempus.Components/{Users => Accounts}/Create/CommandHandlerDecorator.cs (93%) rename PlanTempus.Components/{Users/Create/CreateUserCommand.cs => Accounts/Create/CreateAccountCommand.cs} (68%) rename PlanTempus.Components/{Users/Create/CreateUserController.cs => Accounts/Create/CreateAccountController.cs} (75%) rename PlanTempus.Components/{Users/Create/CreateUserHandler.cs => Accounts/Create/CreateAccountHandler.cs} (72%) rename PlanTempus.Components/{Users/Create/CreateUserResult.cs => Accounts/Create/CreateAccountResult.cs} (64%) rename PlanTempus.Components/{Users/Create/CreateUserValidator.cs => Accounts/Create/CreateAccountValidator.cs} (81%) rename PlanTempus.Components/{Users => Accounts}/Exceptions/EmailAlreadyRegistreredException.cs (50%) rename PlanTempus.X.BDD/FeatureFixtures/{UserRegistrationSpecs.cs => AccountRegistrationSpecs.cs} (52%) create mode 100644 PlanTempus.X.BDD/Scenarios/AccountRegistrationSpecs.cs delete mode 100644 PlanTempus.X.BDD/Scenarios/UserRegistrationSpecs.cs diff --git a/.gitignore b/.gitignore index 1ee5385..8c05519 100644 --- a/.gitignore +++ b/.gitignore @@ -360,3 +360,5 @@ MigrationBackup/ # Fody - auto-generated XML schema FodyWeavers.xsd + +nul diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4798424 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "liveServer.settings.port": 5501 +} \ No newline at end of file diff --git a/Core/Entities/Users/User.cs b/Core/Entities/Accounts/Account.cs similarity index 73% rename from Core/Entities/Users/User.cs rename to Core/Entities/Accounts/Account.cs index 805c028..b991f6b 100644 --- a/Core/Entities/Users/User.cs +++ b/Core/Entities/Accounts/Account.cs @@ -1,12 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace PlanTempus.Core.Entities.Users +namespace PlanTempus.Core.Entities.Accounts { - public class User + public class Account { public int Id { get; set; } public string Email { get; set; } @@ -26,9 +20,9 @@ namespace PlanTempus.Core.Entities.Users 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 DateTime CreatedDate { get; set; } } diff --git a/Database/Core/UserService.cs b/Database/Core/AccountService.cs similarity index 61% rename from Database/Core/UserService.cs rename to Database/Core/AccountService.cs index e74d291..0ed59ba 100644 --- a/Database/Core/UserService.cs +++ b/Database/Core/AccountService.cs @@ -1,24 +1,24 @@ -using Insight.Database; +using Insight.Database; using PlanTempus.Core; -using PlanTempus.Core.Entities.Users; +using PlanTempus.Core.Entities.Accounts; using System.Data; 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; - public UserService(IDbConnection db) + public AccountService(IDbConnection 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, PasswordHash = new SecureTokenizer().TokenizeText(command.Password), @@ -27,13 +27,13 @@ namespace PlanTempus.Database.Core CreatedDate = DateTime.UtcNow }; - var userId = await _db.ExecuteScalarAsync(@$" - INSERT INTO users (email, password_hash, security_stamp, email_confirmed, created_at) + var accountId = await _db.ExecuteScalarAsync(@$" + INSERT INTO accounts (email, password_hash, security_stamp, email_confirmed, created_at) 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"; @@ -46,7 +46,7 @@ namespace PlanTempus.Database.Core { ConnectionString = organizationConnectionString, CreatedDate = DateTime.UtcNow, - CreatedBy = userId, + CreatedBy = accountId, IsActive = true }; @@ -55,17 +55,17 @@ namespace PlanTempus.Database.Core VALUES (@ConnectionString, @CreatedDate, @IsActive) RETURNING id", organization); - // Link user to organization - var userOrganization = new UserOrganization + // Link account to organization + var accountOrganization = new AccountOrganization { - UserId = userId, + AccountId = accountId, OrganizationId = organizationId, CreatedDate = DateTime.UtcNow }; await _db.ExecuteAsync(@$" - INSERT INTO {schema}.user_organizations (user_id, organization_id, created_date) - VALUES (@UserId, @OrganizationId, @CreatedDate)", userOrganization); + INSERT INTO {schema}.account_organizations (account_id, organization_id, created_date) + VALUES (@AccountId, @OrganizationId, @CreatedDate)", accountOrganization); transaction.Commit(); } @@ -76,4 +76,4 @@ namespace PlanTempus.Database.Core } } } -} \ No newline at end of file +} diff --git a/Database/Core/DDL/SetupIdentitySystem.cs b/Database/Core/DDL/SetupIdentitySystem.cs index 01a883f..3547ff2 100644 --- a/Database/Core/DDL/SetupIdentitySystem.cs +++ b/Database/Core/DDL/SetupIdentitySystem.cs @@ -35,9 +35,9 @@ namespace PlanTempus.Database.Core.DDL using var transaction = conn.OpenWithTransaction(); try { - CreateUsersTable(conn); + CreateAccountsTable(conn); CreateOrganizationsTable(conn); - CreateUserOrganizationsTable(conn); + CreateAccountOrganizationsTable(conn); SetupRLS(conn); transaction.Commit(); @@ -51,12 +51,12 @@ namespace PlanTempus.Database.Core.DDL /// - /// Creates the users table + /// Creates the accounts table /// - void CreateUsersTable(IDbConnection db) + void CreateAccountsTable(IDbConnection db) { var sql = @$" - CREATE TABLE IF NOT EXISTS {_command.Schema}.users ( + CREATE TABLE IF NOT EXISTS {_command.Schema}.accounts ( id SERIAL PRIMARY KEY, email VARCHAR(256) NOT NULL UNIQUE, password_hash VARCHAR(256) NOT NULL, @@ -91,17 +91,17 @@ namespace PlanTempus.Database.Core.DDL } /// - /// Creates the user_organizations table + /// Creates the account_organizations table /// - void CreateUserOrganizationsTable(IDbConnection db) + void CreateAccountOrganizationsTable(IDbConnection db) { var sql = @$" - CREATE TABLE IF NOT EXISTS {_command.Schema}.user_organizations ( - user_id INTEGER NOT NULL REFERENCES {_command.Schema}.users(id), + CREATE TABLE IF NOT EXISTS {_command.Schema}.account_organizations ( + account_id INTEGER NOT NULL REFERENCES {_command.Schema}.accounts(id), organization_id INTEGER NOT NULL REFERENCES {_command.Schema}.organizations(id), pin_code VARCHAR(10) NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (user_id, organization_id) + PRIMARY KEY (account_id, organization_id) );"; db.ExecuteSql(sql); @@ -109,24 +109,24 @@ namespace PlanTempus.Database.Core.DDL } /// - /// 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. /// void SetupRLS(IDbConnection db) { var sql = new[] { $"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;", @$"CREATE POLICY organization_access ON {_command.Schema}.organizations USING (id IN ( - SELECT organization_id - FROM {_command.Schema}.user_organizations - WHERE user_id = current_setting('app.user_id', TRUE)::INTEGER + SELECT organization_id + FROM {_command.Schema}.account_organizations + WHERE account_id = current_setting('app.account_id', TRUE)::INTEGER )) WITH CHECK (true);", - $"DROP POLICY IF EXISTS user_organization_access ON {_command.Schema}.user_organizations;", - @$"CREATE POLICY user_organization_access ON {_command.Schema}.user_organizations - USING (user_id = current_setting('app.user_id', TRUE)::INTEGER) WITH CHECK (true);" + $"DROP POLICY IF EXISTS account_organization_access ON {_command.Schema}.account_organizations;", + @$"CREATE POLICY account_organization_access ON {_command.Schema}.account_organizations + USING (account_id = current_setting('app.account_id', TRUE)::INTEGER) WITH CHECK (true);" }; foreach (var statement in sql) diff --git a/PlanTempus.Components/Users/Create/CommandHandlerDecorator.cs b/PlanTempus.Components/Accounts/Create/CommandHandlerDecorator.cs similarity index 93% rename from PlanTempus.Components/Users/Create/CommandHandlerDecorator.cs rename to PlanTempus.Components/Accounts/Create/CommandHandlerDecorator.cs index 6da5c8a..5e4d015 100644 --- a/PlanTempus.Components/Users/Create/CommandHandlerDecorator.cs +++ b/PlanTempus.Components/Accounts/Create/CommandHandlerDecorator.cs @@ -1,9 +1,9 @@ -using System.Diagnostics; +using System.Diagnostics; using Microsoft.ApplicationInsights; using Microsoft.ApplicationInsights.DataContracts; using PlanTempus.Core.CommandQueries; -namespace PlanTempus.Components.Users.Create; +namespace PlanTempus.Components.Accounts.Create; public class CommandHandlerDecorator( ICommandHandler decoratedHandler, @@ -14,30 +14,30 @@ public class CommandHandlerDecorator( command.TransactionId = Guid.NewGuid(); using var operation = telemetryClient.StartOperation($"Handle {decoratedHandler.GetType().FullName}", - command.CorrelationId.ToString()); - try - { - operation.Telemetry.Properties["CorrelationId"] = command.CorrelationId.ToString(); - operation.Telemetry.Properties["TransactionId"] = command.TransactionId.ToString(); - - var result = await decoratedHandler.Handle(command); - - operation.Telemetry.Properties["RequestId"] = result.RequestId.ToString(); - operation.Telemetry.Success = true; - - return result; - } - catch (Exception ex) - { - operation.Telemetry.Success = false; - - telemetryClient.TrackException(ex, new Dictionary - { - ["CorrelationId"] = command.CorrelationId.ToString(), - ["Command"] = command.GetType().Name, - ["CommandHandler"] = decoratedHandler.GetType().FullName - }); - throw; + command.CorrelationId.ToString()); + try + { + operation.Telemetry.Properties["CorrelationId"] = command.CorrelationId.ToString(); + operation.Telemetry.Properties["TransactionId"] = command.TransactionId.ToString(); + + var result = await decoratedHandler.Handle(command); + + operation.Telemetry.Properties["RequestId"] = result.RequestId.ToString(); + operation.Telemetry.Success = true; + + return result; + } + catch (Exception ex) + { + operation.Telemetry.Success = false; + + telemetryClient.TrackException(ex, new Dictionary + { + ["CorrelationId"] = command.CorrelationId.ToString(), + ["Command"] = command.GetType().Name, + ["CommandHandler"] = decoratedHandler.GetType().FullName + }); + throw; } } -} \ No newline at end of file +} diff --git a/PlanTempus.Components/Users/Create/CreateUserCommand.cs b/PlanTempus.Components/Accounts/Create/CreateAccountCommand.cs similarity index 68% rename from PlanTempus.Components/Users/Create/CreateUserCommand.cs rename to PlanTempus.Components/Accounts/Create/CreateAccountCommand.cs index 5d0ef11..99fda2a 100644 --- a/PlanTempus.Components/Users/Create/CreateUserCommand.cs +++ b/PlanTempus.Components/Accounts/Create/CreateAccountCommand.cs @@ -1,11 +1,11 @@ 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 Password { get; set; } public bool IsActive { get; set; } = true; } -} \ No newline at end of file +} diff --git a/PlanTempus.Components/Users/Create/CreateUserController.cs b/PlanTempus.Components/Accounts/Create/CreateAccountController.cs similarity index 75% rename from PlanTempus.Components/Users/Create/CreateUserController.cs rename to PlanTempus.Components/Accounts/Create/CreateAccountController.cs index 487b0dc..db8238b 100644 --- a/PlanTempus.Components/Users/Create/CreateUserController.cs +++ b/PlanTempus.Components/Accounts/Create/CreateAccountController.cs @@ -1,14 +1,14 @@ using Microsoft.AspNetCore.Mvc; using PlanTempus.Core.CommandQueries; -namespace PlanTempus.Components.Users.Create +namespace PlanTempus.Components.Accounts.Create { [ApiController] - [Route("api/users")] - public class CreateUserController(CreateUserHandler handler, CreateUserValidator validator) : ControllerBase + [Route("api/accounts")] + public class CreateAccountController(CreateAccountHandler handler, CreateAccountValidator validator) : ControllerBase { [HttpPost] - public async Task> Create([FromBody] CreateUserCommand command) + public async Task> Create([FromBody] CreateAccountCommand command) { try { @@ -28,4 +28,4 @@ namespace PlanTempus.Components.Users.Create } } } -} \ No newline at end of file +} diff --git a/PlanTempus.Components/Users/Create/CreateUserHandler.cs b/PlanTempus.Components/Accounts/Create/CreateAccountHandler.cs similarity index 72% rename from PlanTempus.Components/Users/Create/CreateUserHandler.cs rename to PlanTempus.Components/Accounts/Create/CreateAccountHandler.cs index 42bb8dc..4264c4f 100644 --- a/PlanTempus.Components/Users/Create/CreateUserHandler.cs +++ b/PlanTempus.Components/Accounts/Create/CreateAccountHandler.cs @@ -1,25 +1,25 @@ using Insight.Database; using Microsoft.ApplicationInsights; using Npgsql; -using PlanTempus.Components.Users.Exceptions; +using PlanTempus.Components.Accounts.Exceptions; using PlanTempus.Core; using PlanTempus.Core.CommandQueries; using PlanTempus.Core.Database; -namespace PlanTempus.Components.Users.Create +namespace PlanTempus.Components.Accounts.Create { - public class CreateUserHandler( + public class CreateAccountHandler( IDatabaseOperations databaseOperations, - ISecureTokenizer secureTokenizer) : ICommandHandler + ISecureTokenizer secureTokenizer) : ICommandHandler { - public async Task Handle(CreateUserCommand command) + public async Task Handle(CreateAccountCommand command) { - using var db = databaseOperations.CreateScope(nameof(CreateUserHandler)); + using var db = databaseOperations.CreateScope(nameof(CreateAccountHandler)); try { var sql = @" - INSERT INTO system.users(email , password_hash, security_stamp, email_confirmed, - access_failed_count, lockout_enabled, + 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) @@ -35,11 +35,10 @@ namespace PlanTempus.Components.Users.Create LockoutEnabled = false, command.IsActive, }); - //lav en mapping mellem requestid og userid - + 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); throw new EmailAlreadyRegistreredException(); @@ -51,4 +50,4 @@ namespace PlanTempus.Components.Users.Create } } } -} \ No newline at end of file +} diff --git a/PlanTempus.Components/Users/Create/CreateUserResult.cs b/PlanTempus.Components/Accounts/Create/CreateAccountResult.cs similarity index 64% rename from PlanTempus.Components/Users/Create/CreateUserResult.cs rename to PlanTempus.Components/Accounts/Create/CreateAccountResult.cs index 24c92c8..a2eea82 100644 --- a/PlanTempus.Components/Users/Create/CreateUserResult.cs +++ b/PlanTempus.Components/Accounts/Create/CreateAccountResult.cs @@ -1,11 +1,11 @@ -namespace PlanTempus.Components.Users.Create +namespace PlanTempus.Components.Accounts.Create { - public class CreateUserResult + public class CreateAccountResult { public long Id { get; set; } public string Email { get; set; } public bool IsActive { get; set; } public DateTime CreatedAt { get; set; } } -} \ No newline at end of file +} diff --git a/PlanTempus.Components/Users/Create/CreateUserValidator.cs b/PlanTempus.Components/Accounts/Create/CreateAccountValidator.cs similarity index 81% rename from PlanTempus.Components/Users/Create/CreateUserValidator.cs rename to PlanTempus.Components/Accounts/Create/CreateAccountValidator.cs index 6007115..1a894fc 100644 --- a/PlanTempus.Components/Users/Create/CreateUserValidator.cs +++ b/PlanTempus.Components/Accounts/Create/CreateAccountValidator.cs @@ -1,10 +1,10 @@ using FluentValidation; -namespace PlanTempus.Components.Users.Create +namespace PlanTempus.Components.Accounts.Create { - public class CreateUserValidator : AbstractValidator + public class CreateAccountValidator : AbstractValidator { - public CreateUserValidator() + public CreateAccountValidator() { RuleFor(x => x.Email) .NotEmpty().WithMessage("Email skal angives.") @@ -20,4 +20,4 @@ namespace PlanTempus.Components.Users.Create .Matches("[^a-zA-Z0-9]").WithMessage("Password skal indeholde mindst ét specialtegn."); } } -} \ No newline at end of file +} diff --git a/PlanTempus.Components/Users/Exceptions/EmailAlreadyRegistreredException.cs b/PlanTempus.Components/Accounts/Exceptions/EmailAlreadyRegistreredException.cs similarity index 50% rename from PlanTempus.Components/Users/Exceptions/EmailAlreadyRegistreredException.cs rename to PlanTempus.Components/Accounts/Exceptions/EmailAlreadyRegistreredException.cs index c098df4..3376cd5 100644 --- a/PlanTempus.Components/Users/Exceptions/EmailAlreadyRegistreredException.cs +++ b/PlanTempus.Components/Accounts/Exceptions/EmailAlreadyRegistreredException.cs @@ -1,6 +1,6 @@ -namespace PlanTempus.Components.Users.Exceptions; +namespace PlanTempus.Components.Accounts.Exceptions; public class EmailAlreadyRegistreredException: Exception { - -} \ No newline at end of file + +} diff --git a/PlanTempus.Components/CommandHandler.cs b/PlanTempus.Components/CommandHandler.cs index 93c55cb..f503920 100644 --- a/PlanTempus.Components/CommandHandler.cs +++ b/PlanTempus.Components/CommandHandler.cs @@ -1,5 +1,4 @@ using Autofac; -using PlanTempus.Components.Users.Create; using PlanTempus.Core.CommandQueries; namespace PlanTempus.Components; diff --git a/PlanTempus.Components/ModuleRegistry/CommandModule.cs b/PlanTempus.Components/ModuleRegistry/CommandModule.cs index 23a1579..8f6c890 100644 --- a/PlanTempus.Components/ModuleRegistry/CommandModule.cs +++ b/PlanTempus.Components/ModuleRegistry/CommandModule.cs @@ -1,5 +1,5 @@ using Autofac; -using PlanTempus.Components.Users.Create; +using PlanTempus.Components.Accounts.Create; using PlanTempus.Core.CommandQueries; using PlanTempus.Core.SeqLogging; diff --git a/PlanTempus.Components/Organizations/Create/CreateOrganizationCommand.cs b/PlanTempus.Components/Organizations/Create/CreateOrganizationCommand.cs index 57234c1..0d7cc81 100644 --- a/PlanTempus.Components/Organizations/Create/CreateOrganizationCommand.cs +++ b/PlanTempus.Components/Organizations/Create/CreateOrganizationCommand.cs @@ -3,6 +3,6 @@ public class CreateOrganizationCommand { public string ConnectionString { get; set; } - public Guid UserId { get; set; } + public Guid AccountId { get; set; } } } diff --git a/PlanTempus.Components/Organizations/Create/CreateOrganizationHandler.cs b/PlanTempus.Components/Organizations/Create/CreateOrganizationHandler.cs index 06517f2..abd91b0 100644 --- a/PlanTempus.Components/Organizations/Create/CreateOrganizationHandler.cs +++ b/PlanTempus.Components/Organizations/Create/CreateOrganizationHandler.cs @@ -26,9 +26,9 @@ namespace PlanTempus.Components.Organizations.Create var orgResult = data.First(); await db.Connection.ExecuteAsync(@$" - INSERT INTO user_organizations (user_id, organization_id) - VALUES (@UserId, @OrganizationId)", - new { command.UserId, OrganizationId = orgResult.Id }); + INSERT INTO account_organizations (account_id, organization_id) + VALUES (@AccountId, @OrganizationId)", + new { command.AccountId, OrganizationId = orgResult.Id }); transaction.Commit(); diff --git a/PlanTempus.X.BDD/Class1.cs b/PlanTempus.X.BDD/Class1.cs index 6a7a5b6..b10a77b 100644 --- a/PlanTempus.X.BDD/Class1.cs +++ b/PlanTempus.X.BDD/Class1.cs @@ -1,4 +1,4 @@ -// PlanTempus.X.Services.cs +// PlanTempus.X.Services.cs using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -6,11 +6,10 @@ using System.Threading.Tasks; namespace PlanTempus.X.Services { // Models - public class User + public class Account { public string Id { get; set; } public string Email { get; set; } - public string Name { get; set; } public bool EmailConfirmed { get; set; } public string Password { get; set; } public bool IsLocked { get; set; } @@ -24,19 +23,19 @@ namespace PlanTempus.X.Services 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 Role { get; set; } } // Service interfaces - public interface IUserService + public interface IAccountService { - Task CreateUserAsync(string email, string name); - Task GetUserByEmailAsync(string email); - User GetUserByEmail(string email); + Task CreateAccountAsync(string email, string password); + Task GetAccountByEmailAsync(string email); + Account GetAccountByEmail(string email); Task ConfirmEmailAsync(string confirmationToken); } @@ -48,14 +47,14 @@ namespace PlanTempus.X.Services public interface IOrganizationService { - Task SetupOrganizationAsync(string userId, string orgName, string password); - Task CreateOrganizationAsync(string userId, string orgName); + Task SetupOrganizationAsync(string accountId, string orgName, string password); + Task CreateOrganizationAsync(string accountId, string orgName); } - public interface IUserOrganizationService + public interface IAccountOrganizationService { - UserOrganization GetUserOrganization(string userId, string organizationId); - List GetUserOrganizations(string userId); + AccountOrganization GetAccountOrganization(string accountId, string organizationId); + List GetAccountOrganizations(string accountId); } public interface ITenantService @@ -65,26 +64,26 @@ namespace PlanTempus.X.Services public interface IAuthService { - bool IsUserAuthenticated(string userId); + bool IsAccountAuthenticated(string accountId); Task AttemptLoginAsync(string email, string password); } // Service implementations - public class UserService : IUserService + public class AccountService : IAccountService { - public async Task CreateUserAsync(string email, string name) + public async Task CreateAccountAsync(string email, string password) { - throw new NotImplementedException("CreateUserAsync not implemented"); + throw new NotImplementedException("CreateAccountAsync not implemented"); } - public async Task GetUserByEmailAsync(string email) + public async Task 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) @@ -108,27 +107,27 @@ namespace PlanTempus.X.Services public class OrganizationService : IOrganizationService { - public async Task SetupOrganizationAsync(string userId, string orgName, string password) + public async Task SetupOrganizationAsync(string accountId, string orgName, string password) { throw new NotImplementedException("SetupOrganizationAsync not implemented"); } - public async Task CreateOrganizationAsync(string userId, string orgName) + public async Task CreateOrganizationAsync(string accountId, string orgName) { 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 GetUserOrganizations(string userId) + public List 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 bool IsUserAuthenticated(string userId) + public bool IsAccountAuthenticated(string accountId) { - throw new NotImplementedException("IsUserAuthenticated not implemented"); + throw new NotImplementedException("IsAccountAuthenticated not implemented"); } public async Task AttemptLoginAsync(string email, string password) @@ -152,4 +151,4 @@ namespace PlanTempus.X.Services throw new NotImplementedException("AttemptLoginAsync not implemented"); } } -} \ No newline at end of file +} diff --git a/PlanTempus.X.BDD/FeatureFixtures/UserRegistrationSpecs.cs b/PlanTempus.X.BDD/FeatureFixtures/AccountRegistrationSpecs.cs similarity index 52% rename from PlanTempus.X.BDD/FeatureFixtures/UserRegistrationSpecs.cs rename to PlanTempus.X.BDD/FeatureFixtures/AccountRegistrationSpecs.cs index 2c822db..dc5ab12 100644 --- a/PlanTempus.X.BDD/FeatureFixtures/UserRegistrationSpecs.cs +++ b/PlanTempus.X.BDD/FeatureFixtures/AccountRegistrationSpecs.cs @@ -1,4 +1,4 @@ -using LightBDD.Framework; +using LightBDD.Framework; using LightBDD.Framework.Scenarios; using LightBDD.MsTest3; using PlanTempus.X.Services; @@ -9,29 +9,29 @@ namespace PlanTempus.X.BDD.FeatureFixtures; [FeatureDescription(@"As a new user I want to register with my email 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 Exception _registrationError; - IUserService _userService; + IAccountService _accountService; IEmailService _emailService; 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 - var user = await _userService.GetUserByEmailAsync(email); - user.ShouldBeNull(); + // Ensure account doesn't exist with email + var account = await _accountService.GetAccountByEmailAsync(email); + account.ShouldBeNull(); _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 { - _currentUser = await _userService.CreateUserAsync(email, name); + _currentAccount = await _accountService.CreateAccountAsync(email, password); _currentEmail = email; } catch (Exception ex) @@ -44,7 +44,7 @@ public partial class UserRegistrationSpecs : FeatureFixture { try { - _currentUser = await _userService.CreateUserAsync(email, "Test User"); + _currentAccount = await _accountService.CreateAccountAsync(email, "TestPassword123!"); _currentEmail = email; } 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(); - _currentUser.Email.ShouldBe(email); - _currentUser.EmailConfirmed.ShouldBe(confirmationStatus); + _currentAccount.ShouldNotBeNull(); + _currentAccount.Email.ShouldBe(email); + _currentAccount.EmailConfirmed.ShouldBe(confirmationStatus); await Task.CompletedTask; - } public async Task Then_a_confirmation_email_should_be_sent() @@ -69,18 +68,16 @@ public partial class UserRegistrationSpecs : FeatureFixture emailSent.ShouldBeTrue(); 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 - _currentUser = await _userService.CreateUserAsync(email, "Existing User"); - _currentUser.ShouldNotBeNull(); + // Create an account first to ensure it exists + _currentAccount = await _accountService.CreateAccountAsync(email, "ExistingPassword123!"); + _currentAccount.ShouldNotBeNull(); _currentEmail = email; await Task.CompletedTask; - } public async Task Then_registration_should_fail_with_error(string expectedErrorMessage) @@ -89,6 +86,5 @@ public partial class UserRegistrationSpecs : FeatureFixture _registrationError.Message.ShouldBe(expectedErrorMessage); await Task.CompletedTask; - } } diff --git a/PlanTempus.X.BDD/FeatureFixtures/AccountSecuritySpecs.cs b/PlanTempus.X.BDD/FeatureFixtures/AccountSecuritySpecs.cs index f0902a0..91cc54e 100644 --- a/PlanTempus.X.BDD/FeatureFixtures/AccountSecuritySpecs.cs +++ b/PlanTempus.X.BDD/FeatureFixtures/AccountSecuritySpecs.cs @@ -5,28 +5,29 @@ 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")] + +[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; + IAccountService _accountService; IEmailService _emailService; IOrganizationService _organizationService; IAuthService _authService; - protected User _currentUser; + protected Account _currentAccount; protected DateTime? _lockoutEnd; protected bool _isLocked; protected bool _loginSuccessful; - public async Task Given_user_exists(string email) + public async Task Given_account_exists(string email) { - _currentUser = await _userService.GetUserByEmailAsync(email); - if (_currentUser == null) + _currentAccount = await _accountService.GetAccountByEmailAsync(email); + 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 { - await _authService.AttemptLoginAsync(_currentUser.Email, "WrongPassword"); + await _authService.AttemptLoginAsync(_currentAccount.Email, "WrongPassword"); } catch { @@ -47,29 +48,27 @@ public partial class AccountSecuritySpecs : FeatureFixture public async Task Then_the_account_should_be_locked() { - _currentUser = _userService.GetUserByEmail(_currentUser.Email); - _isLocked = _currentUser.IsLocked; + _currentAccount = _accountService.GetAccountByEmail(_currentAccount.Email); + _isLocked = _currentAccount.IsLocked; _isLocked.ShouldBeTrue(); await Task.CompletedTask; - } public async Task And_lockout_end_should_be_set() { - _lockoutEnd = _currentUser.LockoutEnd; + _lockoutEnd = _currentAccount.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); + _loginSuccessful = await _authService.AttemptLoginAsync(_currentAccount.Email, _currentAccount.Password); } catch { @@ -78,4 +77,4 @@ public partial class AccountSecuritySpecs : FeatureFixture _loginSuccessful.ShouldBeFalse(); } -} \ No newline at end of file +} diff --git a/PlanTempus.X.BDD/FeatureFixtures/EmailConfirmationSpecs.cs b/PlanTempus.X.BDD/FeatureFixtures/EmailConfirmationSpecs.cs index 4e2bf49..d90a7cb 100644 --- a/PlanTempus.X.BDD/FeatureFixtures/EmailConfirmationSpecs.cs +++ b/PlanTempus.X.BDD/FeatureFixtures/EmailConfirmationSpecs.cs @@ -5,40 +5,41 @@ using PlanTempus.X.Services; using Shouldly; namespace PlanTempus.X.BDD.FeatureFixtures; - [TestClass] + +[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; + IAccountService _accountService; IEmailService _emailService; IOrganizationService _organizationService; - protected User _currentUser; + protected Account _currentAccount; protected string _currentEmail; protected string _confirmationLink; protected bool _redirectedToWelcome; 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"); - _currentUser.EmailConfirmed.ShouldBeFalse(); + _currentAccount = await _accountService.CreateAccountAsync(email, "TestPassword123!"); + _currentAccount.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); + await _accountService.ConfirmEmailAsync(_confirmationLink); _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); - _currentUser.EmailConfirmed.ShouldBeTrue(); + _currentAccount = _accountService.GetAccountByEmail(_currentEmail); + _currentAccount.EmailConfirmed.ShouldBeTrue(); } public async Task And_I_should_be_redirected_to_the_welcome_page() @@ -50,7 +51,7 @@ public partial class EmailConfirmationSpecs : FeatureFixture { try { - await _userService.ConfirmEmailAsync("invalid-confirmation-token"); + await _accountService.ConfirmEmailAsync("invalid-confirmation-token"); } catch (Exception ex) { @@ -65,9 +66,9 @@ public partial class EmailConfirmationSpecs : FeatureFixture public async Task And_my_email_remains_unconfirmed() { - if (_currentUser != null) + if (_currentAccount != null) { - _currentUser.EmailConfirmed.ShouldBeFalse(); + _currentAccount.EmailConfirmed.ShouldBeFalse(); } } -} \ No newline at end of file +} diff --git a/PlanTempus.X.BDD/FeatureFixtures/OrganizationSetupSpecs.cs b/PlanTempus.X.BDD/FeatureFixtures/OrganizationSetupSpecs.cs index e59536f..6a28d36 100644 --- a/PlanTempus.X.BDD/FeatureFixtures/OrganizationSetupSpecs.cs +++ b/PlanTempus.X.BDD/FeatureFixtures/OrganizationSetupSpecs.cs @@ -5,38 +5,39 @@ 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; + IAccountService _accountService; IEmailService _emailService; IOrganizationService _organizationService; - IUserOrganizationService _userOrganizationService; + IAccountOrganizationService _accountOrganizationService; ITenantService _tenantService; IAuthService _authService; - protected User _currentUser; + protected Account _currentAccount; protected Organization _organization; protected Exception _setupError; - protected List _userOrganizations; + protected List _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 - _currentUser = await _userService.CreateUserAsync(email, "Test User"); + // Create an account with confirmed email + _currentAccount = await _accountService.CreateAccountAsync(email, "TestPassword123!"); var confirmationLink = await _emailService.GetConfirmationLinkForEmail(email); - await _userService.ConfirmEmailAsync(confirmationLink); - _currentUser.EmailConfirmed.ShouldBeTrue(); + await _accountService.ConfirmEmailAsync(confirmationLink); + _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 { - _organization = await _organizationService.SetupOrganizationAsync(_currentUser.Id, orgName, password); + _organization = await _organizationService.SetupOrganizationAsync(_currentAccount.Id, orgName, password); } catch (Exception ex) { @@ -48,18 +49,17 @@ public partial class OrganizationSetupSpecs : FeatureFixture { _organization.ShouldNotBeNull(); _organization.Name.ShouldBe("Acme Corp"); - _organization.CreatedBy.ShouldBe(_currentUser.Id); + _organization.CreatedBy.ShouldBe(_currentAccount.Id); 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); - userOrg.ShouldNotBeNull(); + var accountOrg = _accountOrganizationService.GetAccountOrganization(_currentAccount.Id, _organization.Id); + accountOrg.ShouldNotBeNull(); await Task.CompletedTask; - } public async Task And_tenant_tables_should_be_created_for_the_organization() @@ -68,23 +68,21 @@ public partial class OrganizationSetupSpecs : FeatureFixture tenantTablesExist.ShouldBeTrue(); 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(); 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 { - await _organizationService.SetupOrganizationAsync(_currentUser.Id, orgName, ""); + await _organizationService.SetupOrganizationAsync(_currentAccount.Id, orgName, ""); } catch (Exception ex) { @@ -98,37 +96,34 @@ public partial class OrganizationSetupSpecs : FeatureFixture _setupError.Message.ShouldBe(expectedErrorMessage); 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 When_user_submit_organization_name_and_valid_password("First Org", "ValidP@ssw0rd"); - _userOrganizations = new List { _organization }; + await Given_account_has_confirmed_their_email(email); + await When_account_submit_organization_name_and_valid_password("First Org", "ValidP@ssw0rd"); + _accountOrganizations = new List { _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); - _userOrganizations.Add(newOrg); + var newOrg = await _organizationService.CreateOrganizationAsync(_currentAccount.Id, orgName); + _accountOrganizations.Add(newOrg); } public async Task Then_a_new_organization_entry_should_be_created() { - _userOrganizations.Count.ShouldBe(2); - _userOrganizations[1].Name.ShouldBe("Second Org"); + _accountOrganizations.Count.ShouldBe(2); + _accountOrganizations[1].Name.ShouldBe("Second Org"); 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); - userOrgs.Count.ShouldBe(2); + var accountOrgs = _accountOrganizationService.GetAccountOrganizations(_currentAccount.Id); + accountOrgs.Count.ShouldBe(2); await Task.CompletedTask; - } -} \ No newline at end of file +} diff --git a/PlanTempus.X.BDD/Scenarios/AccountRegistrationSpecs.cs b/PlanTempus.X.BDD/Scenarios/AccountRegistrationSpecs.cs new file mode 100644 index 0000000..23f2a7d --- /dev/null +++ b/PlanTempus.X.BDD/Scenarios/AccountRegistrationSpecs.cs @@ -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") + ); + } +} diff --git a/PlanTempus.X.BDD/Scenarios/AccountSecuritySpecs.cs b/PlanTempus.X.BDD/Scenarios/AccountSecuritySpecs.cs index e446c18..6f0a16f 100644 --- a/PlanTempus.X.BDD/Scenarios/AccountSecuritySpecs.cs +++ b/PlanTempus.X.BDD/Scenarios/AccountSecuritySpecs.cs @@ -3,19 +3,20 @@ using LightBDD.Framework.Scenarios; using LightBDD.MsTest3; namespace PlanTempus.X.BDD.Scenarios; - [TestClass] + +[TestClass] public partial class AccountSecuritySpecs : FeatureFixtures.AccountSecuritySpecs { [Scenario] [TestMethod] - public async Task User_lockout_after_multiple_failed_attempts() + public async Task Account_lockout_after_multiple_failed_attempts() { await Runner.RunScenarioAsync( - _ => Given_user_exists("test@example.com"), + _ => Given_account_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() ); } -} \ No newline at end of file +} diff --git a/PlanTempus.X.BDD/Scenarios/EmailConfirmationSpecs.cs b/PlanTempus.X.BDD/Scenarios/EmailConfirmationSpecs.cs index 2565735..1594faf 100644 --- a/PlanTempus.X.BDD/Scenarios/EmailConfirmationSpecs.cs +++ b/PlanTempus.X.BDD/Scenarios/EmailConfirmationSpecs.cs @@ -3,7 +3,8 @@ using LightBDD.Framework.Scenarios; using LightBDD.MsTest3; namespace PlanTempus.X.BDD.Scenarios; - [TestClass] + +[TestClass] public partial class EmailConfirmationSpecs : FeatureFixtures.EmailConfirmationSpecs { [Scenario] @@ -11,9 +12,9 @@ public partial class EmailConfirmationSpecs : FeatureFixtures.EmailConfirmationS public async Task Confirm_valid_email_address() { 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"), - _ => 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() ); } @@ -28,4 +29,4 @@ public partial class EmailConfirmationSpecs : FeatureFixtures.EmailConfirmationS _ => And_my_email_remains_unconfirmed() ); } -} \ No newline at end of file +} diff --git a/PlanTempus.X.BDD/Scenarios/OrganizationSetupSpecs.cs b/PlanTempus.X.BDD/Scenarios/OrganizationSetupSpecs.cs index 4c82cee..8a2c3ed 100644 --- a/PlanTempus.X.BDD/Scenarios/OrganizationSetupSpecs.cs +++ b/PlanTempus.X.BDD/Scenarios/OrganizationSetupSpecs.cs @@ -3,7 +3,8 @@ using LightBDD.Framework.Scenarios; using LightBDD.MsTest3; namespace PlanTempus.X.BDD.Scenarios; - [TestClass] + +[TestClass] public partial class OrganizationSetupSpecs : FeatureFixtures.OrganizationSetupSpecs { [Scenario] @@ -11,12 +12,12 @@ public partial class OrganizationSetupSpecs : FeatureFixtures.OrganizationSetupS 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"), + _ => Given_account_has_confirmed_their_email("test@example.com"), + _ => When_account_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_the_account_should_be_linked_to_the_organization_in_account_organizations(), _ => 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() { await Runner.RunScenarioAsync( - _ => Given_user_has_confirmed_their_email("test@example.com"), - _ => When_user_submit_organization_name_without_password("Acme Corp"), + _ => Given_account_has_confirmed_their_email("test@example.com"), + _ => When_account_submit_organization_name_without_password("Acme Corp"), _ => 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() { await Runner.RunScenarioAsync( - _ => Given_user_has_completed_initial_setup("test@example.com"), - _ => When_user_create_a_new_organization("Second Org"), + _ => Given_account_has_completed_initial_setup("test@example.com"), + _ => When_account_create_a_new_organization("Second Org"), _ => 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() ); } -} \ No newline at end of file +} diff --git a/PlanTempus.X.BDD/Scenarios/UserRegistrationSpecs.cs b/PlanTempus.X.BDD/Scenarios/UserRegistrationSpecs.cs deleted file mode 100644 index 3138b6f..0000000 --- a/PlanTempus.X.BDD/Scenarios/UserRegistrationSpecs.cs +++ /dev/null @@ -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") - ); - } -} diff --git a/SetupInfrastructure/appconfiguration.json b/SetupInfrastructure/appconfiguration.json index 9bb0266..8fa50ec 100644 --- a/SetupInfrastructure/appconfiguration.json +++ b/SetupInfrastructure/appconfiguration.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "DefaultConnection": "Host=192.168.1.57;Port=5432;Database=ptmain;" + "DefaultConnection": "Host=192.168.1.63;Port=5432;Database=ptmain;" }, "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", diff --git a/Tests/CommandQueryHandlerTests/HandlerTest.cs b/Tests/CommandQueryHandlerTests/HandlerTest.cs index 8fde9c9..7fb9607 100644 --- a/Tests/CommandQueryHandlerTests/HandlerTest.cs +++ b/Tests/CommandQueryHandlerTests/HandlerTest.cs @@ -1,7 +1,7 @@ using Autofac; using Insight.Database; using PlanTempus.Components; -using PlanTempus.Components.Users.Create; +using PlanTempus.Components.Accounts.Create; using PlanTempus.Core.CommandQueries; using PlanTempus.Core.Database; using PlanTempus.Core.Database.ConnectionFactory; @@ -22,7 +22,7 @@ public class HandlerTest : TestFixture var commandHandler = Container.Resolve(); commandHandler.ShouldBeOfType(); - var command = new CreateUserCommand + var command = new CreateAccountCommand { Email = $"{GetRandomWord()}@dumbanddumber.com5", // Lloyd Christmas Password = "1234AceVentura#LOL", // Ace Ventura @@ -31,7 +31,7 @@ public class HandlerTest : TestFixture }; var result = await commandHandler.Handle(command); - + result.ShouldNotBeNull(); } } \ No newline at end of file diff --git a/Tests/PostgresTests.cs b/Tests/PostgresTests.cs index 933fd80..35e52a4 100644 --- a/Tests/PostgresTests.cs +++ b/Tests/PostgresTests.cs @@ -1,6 +1,6 @@ using Autofac; using Insight.Database; -using PlanTempus.Components.Users.Exceptions; +using PlanTempus.Components.Accounts.Exceptions; using PlanTempus.Core.Database; using PlanTempus.Core.Database.ConnectionFactory; using Shouldly; @@ -63,12 +63,12 @@ public class PostgresTests : TestFixture } [TestMethod] - public async Task TestForUniqueUserEmail() + public async Task TestForUniqueAccountEmail() { - using var db = _databaseOperations.CreateScope(nameof(TestForUniqueUserEmail)); + using var db = _databaseOperations.CreateScope(nameof(TestForUniqueAccountEmail)); 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) RETURNING id, created_at, email, is_active"; @@ -83,18 +83,18 @@ public class PostgresTests : TestFixture IsActive = true }; - var user = await db.Connection.QuerySqlAsync(sql, parameters); + var account = await db.Connection.QuerySqlAsync(sql, parameters); //EmailAlreadyRegistreredException //try insert, to test exception var ex = await Should.ThrowAsync(async () => await db.Connection.QuerySqlAsync(sql, parameters)); - ex.ConstraintName.ShouldBe("users_email_key"); - } + ex.ConstraintName.ShouldBe("accounts_email_key"); + } catch (Exception ex) - { - db.Error(ex); - + { + db.Error(ex); + } }