2026-01-10 11:13:33 +01:00
|
|
|
using Autofac;
|
|
|
|
|
using Insight.Database;
|
2025-03-03 00:42:20 +01:00
|
|
|
using LightBDD.Framework;
|
|
|
|
|
using LightBDD.Framework.Scenarios;
|
|
|
|
|
using LightBDD.MsTest3;
|
2026-01-10 11:13:33 +01:00
|
|
|
using PlanTempus.Components;
|
|
|
|
|
using PlanTempus.Components.Accounts.ConfirmEmail;
|
|
|
|
|
using PlanTempus.Components.Accounts.Create;
|
|
|
|
|
using PlanTempus.Core.Database;
|
|
|
|
|
using PlanTempus.Core.Outbox;
|
2025-03-03 00:42:20 +01:00
|
|
|
using Shouldly;
|
|
|
|
|
|
|
|
|
|
namespace PlanTempus.X.BDD.FeatureFixtures;
|
2026-01-09 22:14:46 +01:00
|
|
|
|
|
|
|
|
[TestClass]
|
2025-03-03 00:42:20 +01:00
|
|
|
[FeatureDescription(@"As a registered user
|
|
|
|
|
I want to confirm my email
|
|
|
|
|
So I can activate my account")]
|
2026-01-10 11:13:33 +01:00
|
|
|
public partial class EmailConfirmationSpecs : BddTestFixture
|
2025-03-03 00:42:20 +01:00
|
|
|
{
|
|
|
|
|
protected string _currentEmail;
|
2026-01-10 11:13:33 +01:00
|
|
|
protected string _securityStamp;
|
|
|
|
|
protected bool _emailConfirmed;
|
2025-03-03 00:42:20 +01:00
|
|
|
protected string _errorMessage;
|
2026-01-10 11:13:33 +01:00
|
|
|
protected bool _outboxEntryCreated;
|
2025-03-03 00:42:20 +01:00
|
|
|
|
2026-01-09 22:14:46 +01:00
|
|
|
public async Task Given_an_account_exists_with_unconfirmed_email(string email)
|
2025-03-03 00:42:20 +01:00
|
|
|
{
|
|
|
|
|
_currentEmail = email;
|
2026-01-10 11:13:33 +01:00
|
|
|
|
|
|
|
|
var command = new CreateAccountCommand
|
|
|
|
|
{
|
|
|
|
|
CorrelationId = Guid.NewGuid(),
|
|
|
|
|
Email = email,
|
|
|
|
|
Password = "TestPassword123!"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await CommandHandler.Handle(command);
|
|
|
|
|
|
|
|
|
|
// Hent security_stamp fra database til brug i confirmation
|
|
|
|
|
var db = Container.Resolve<IDatabaseOperations>();
|
|
|
|
|
using var scope = db.CreateScope(nameof(EmailConfirmationSpecs));
|
|
|
|
|
|
|
|
|
|
var result = await scope.Connection.QuerySqlAsync<AccountDto>(
|
|
|
|
|
"SELECT email_confirmed, security_stamp FROM system.accounts WHERE email = @Email",
|
|
|
|
|
new { Email = email });
|
|
|
|
|
|
|
|
|
|
result.Count.ShouldBe(1);
|
|
|
|
|
result[0].EmailConfirmed.ShouldBeFalse();
|
|
|
|
|
_securityStamp = result[0].SecurityStamp;
|
2025-03-03 00:42:20 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-10 11:13:33 +01:00
|
|
|
public async Task And_a_verification_email_is_queued_in_outbox()
|
2025-03-03 00:42:20 +01:00
|
|
|
{
|
2026-01-10 11:13:33 +01:00
|
|
|
var db = Container.Resolve<IDatabaseOperations>();
|
|
|
|
|
using var scope = db.CreateScope(nameof(EmailConfirmationSpecs));
|
|
|
|
|
|
|
|
|
|
var result = await scope.Connection.QuerySqlAsync<OutboxDto>(
|
|
|
|
|
@"SELECT type, payload FROM system.outbox
|
|
|
|
|
WHERE type = @Type AND payload->>'Email' = @Email",
|
|
|
|
|
new { Type = OutboxMessageTypes.VerificationEmail, Email = _currentEmail });
|
|
|
|
|
|
|
|
|
|
result.Count.ShouldBeGreaterThan(0);
|
|
|
|
|
_outboxEntryCreated = true;
|
2025-03-03 00:42:20 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-10 11:13:33 +01:00
|
|
|
public async Task And_the_outbox_message_is_processed()
|
2025-03-03 00:42:20 +01:00
|
|
|
{
|
2026-01-10 11:13:33 +01:00
|
|
|
// Vent på at OutboxListener når at behandle beskeden
|
|
|
|
|
await Task.Delay(1000);
|
|
|
|
|
|
|
|
|
|
var db = Container.Resolve<IDatabaseOperations>();
|
|
|
|
|
using var scope = db.CreateScope(nameof(EmailConfirmationSpecs));
|
|
|
|
|
|
|
|
|
|
var result = await scope.Connection.QuerySqlAsync<OutboxDto>(
|
|
|
|
|
@"SELECT status FROM system.outbox
|
|
|
|
|
WHERE type = @Type AND payload->>'Email' = @Email",
|
|
|
|
|
new { Type = OutboxMessageTypes.VerificationEmail, Email = _currentEmail });
|
|
|
|
|
|
|
|
|
|
result.Count.ShouldBeGreaterThan(0);
|
|
|
|
|
// Status skal være 'sent' eller 'failed' - begge indikerer at beskeden blev behandlet
|
|
|
|
|
result[0].Status.ShouldBeOneOf("sent", "failed");
|
2025-03-03 00:42:20 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-10 11:13:33 +01:00
|
|
|
public async Task When_I_confirm_email_with_valid_token()
|
2025-03-03 00:42:20 +01:00
|
|
|
{
|
2026-01-10 11:13:33 +01:00
|
|
|
var command = new ConfirmEmailCommand
|
|
|
|
|
{
|
|
|
|
|
CorrelationId = Guid.NewGuid(),
|
|
|
|
|
Email = _currentEmail,
|
|
|
|
|
Token = _securityStamp
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await CommandHandler.Handle(command);
|
2025-03-03 00:42:20 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-10 11:13:33 +01:00
|
|
|
public async Task When_I_confirm_email_with_invalid_token()
|
2025-03-03 00:42:20 +01:00
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2026-01-10 11:13:33 +01:00
|
|
|
var command = new ConfirmEmailCommand
|
|
|
|
|
{
|
|
|
|
|
CorrelationId = Guid.NewGuid(),
|
|
|
|
|
Email = _currentEmail ?? "unknown@example.com",
|
|
|
|
|
Token = "invalid-token"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await CommandHandler.Handle(command);
|
2025-03-03 00:42:20 +01:00
|
|
|
}
|
2026-01-10 11:13:33 +01:00
|
|
|
catch (InvalidTokenException ex)
|
2025-03-03 00:42:20 +01:00
|
|
|
{
|
|
|
|
|
_errorMessage = ex.Message;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-10 11:13:33 +01:00
|
|
|
public async Task Then_the_accounts_email_should_be_confirmed()
|
2025-03-03 00:42:20 +01:00
|
|
|
{
|
2026-01-10 11:13:33 +01:00
|
|
|
var db = Container.Resolve<IDatabaseOperations>();
|
|
|
|
|
using var scope = db.CreateScope(nameof(EmailConfirmationSpecs));
|
|
|
|
|
|
|
|
|
|
var result = await scope.Connection.QuerySqlAsync<AccountDto>(
|
|
|
|
|
"SELECT email_confirmed FROM system.accounts WHERE email = @Email",
|
|
|
|
|
new { Email = _currentEmail });
|
|
|
|
|
|
|
|
|
|
result.Count.ShouldBe(1);
|
|
|
|
|
result[0].EmailConfirmed.ShouldBeTrue();
|
2025-03-03 00:42:20 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-10 11:13:33 +01:00
|
|
|
public async Task Then_I_should_see_an_error_message(string expectedMessage)
|
2025-03-03 00:42:20 +01:00
|
|
|
{
|
2026-01-10 11:13:33 +01:00
|
|
|
_errorMessage.ShouldNotBeNull();
|
|
|
|
|
_errorMessage.ShouldContain(expectedMessage);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task And_the_email_remains_unconfirmed()
|
|
|
|
|
{
|
|
|
|
|
if (_currentEmail == null) return;
|
|
|
|
|
|
|
|
|
|
var db = Container.Resolve<IDatabaseOperations>();
|
|
|
|
|
using var scope = db.CreateScope(nameof(EmailConfirmationSpecs));
|
|
|
|
|
|
|
|
|
|
var result = await scope.Connection.QuerySqlAsync<AccountDto>(
|
|
|
|
|
"SELECT email_confirmed FROM system.accounts WHERE email = @Email",
|
|
|
|
|
new { Email = _currentEmail });
|
|
|
|
|
|
|
|
|
|
if (result.Count > 0)
|
2025-03-03 00:42:20 +01:00
|
|
|
{
|
2026-01-10 11:13:33 +01:00
|
|
|
result[0].EmailConfirmed.ShouldBeFalse();
|
2025-03-03 00:42:20 +01:00
|
|
|
}
|
|
|
|
|
}
|
2026-01-10 11:13:33 +01:00
|
|
|
|
|
|
|
|
private class AccountDto
|
|
|
|
|
{
|
|
|
|
|
public bool EmailConfirmed { get; set; }
|
|
|
|
|
public string SecurityStamp { get; set; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class OutboxDto
|
|
|
|
|
{
|
|
|
|
|
public string Type { get; set; }
|
|
|
|
|
public string Payload { get; set; }
|
|
|
|
|
public string Status { get; set; }
|
|
|
|
|
}
|
2026-01-09 22:14:46 +01:00
|
|
|
}
|