mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
39 changed files with 850 additions and 38 deletions
@ -0,0 +1,21 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Shared.Users; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Invitation |
||||
|
{ |
||||
|
public interface IInvitationEmailSender |
||||
|
{ |
||||
|
bool IsActive { get; } |
||||
|
|
||||
|
Task SendNewUserEmailAsync(IUser assigner, IUser assignee, string appName); |
||||
|
|
||||
|
Task SendExistingUserEmailAsync(IUser assigner, IUser assignee, string appName); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,105 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Events.Apps; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
using Squidex.Infrastructure.Log; |
||||
|
using Squidex.Infrastructure.Tasks; |
||||
|
using Squidex.Shared.Users; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Invitation |
||||
|
{ |
||||
|
public sealed class InvitationEmailEventConsumer : IEventConsumer |
||||
|
{ |
||||
|
private readonly IInvitationEmailSender emailSender; |
||||
|
private readonly IUserResolver userResolver; |
||||
|
private readonly ISemanticLog log; |
||||
|
|
||||
|
public string Name |
||||
|
{ |
||||
|
get { return "InvitationEmailSender"; } |
||||
|
} |
||||
|
|
||||
|
public string EventsFilter |
||||
|
{ |
||||
|
get { return "^app-"; } |
||||
|
} |
||||
|
|
||||
|
public InvitationEmailEventConsumer(IInvitationEmailSender emailSender, IUserResolver userResolver, ISemanticLog log) |
||||
|
{ |
||||
|
Guard.NotNull(emailSender, nameof(emailSender)); |
||||
|
Guard.NotNull(userResolver, nameof(userResolver)); |
||||
|
Guard.NotNull(log, nameof(log)); |
||||
|
|
||||
|
this.emailSender = emailSender; |
||||
|
this.userResolver = userResolver; |
||||
|
|
||||
|
this.log = log; |
||||
|
} |
||||
|
|
||||
|
public bool Handles(StoredEvent @event) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public Task ClearAsync() |
||||
|
{ |
||||
|
return TaskHelper.Done; |
||||
|
} |
||||
|
|
||||
|
public async Task On(Envelope<IEvent> @event) |
||||
|
{ |
||||
|
if (!emailSender.IsActive) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (@event.Payload is AppContributorAssigned appContributorAssigned && appContributorAssigned.Actor.IsSubject) |
||||
|
{ |
||||
|
var assignerId = appContributorAssigned.Actor.Identifier; |
||||
|
var assigneeId = appContributorAssigned.ContributorId; |
||||
|
|
||||
|
var assigner = await userResolver.FindByIdOrEmailAsync(assignerId); |
||||
|
|
||||
|
if (assigner == null) |
||||
|
{ |
||||
|
LogWarning($"Assigner {assignerId} not found"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var assignee = await userResolver.FindByIdOrEmailAsync(appContributorAssigned.ContributorId); |
||||
|
|
||||
|
if (assignee == null) |
||||
|
{ |
||||
|
LogWarning($"Assignee {assigneeId} not found"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var appName = appContributorAssigned.AppId.Name; |
||||
|
|
||||
|
if (appContributorAssigned.IsCreated) |
||||
|
{ |
||||
|
await emailSender.SendNewUserEmailAsync(assigner, assignee, appName); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
await emailSender.SendExistingUserEmailAsync(assigner, assignee, appName); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void LogWarning(string reason) |
||||
|
{ |
||||
|
log.LogWarning(w => w |
||||
|
.WriteProperty("action", "InviteUser") |
||||
|
.WriteProperty("status", "Failed") |
||||
|
.WriteProperty("reason", reason)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,107 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Email; |
||||
|
using Squidex.Infrastructure.Log; |
||||
|
using Squidex.Shared.Users; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Invitation |
||||
|
{ |
||||
|
public sealed class InvitationEmailSender : IInvitationEmailSender |
||||
|
{ |
||||
|
private readonly IEmailSender emailSender; |
||||
|
private readonly IEmailUrlGenerator emailUrlGenerator; |
||||
|
private readonly ISemanticLog log; |
||||
|
private readonly InvitationEmailTextOptions texts; |
||||
|
|
||||
|
public bool IsActive |
||||
|
{ |
||||
|
get { return true; } |
||||
|
} |
||||
|
|
||||
|
public InvitationEmailSender( |
||||
|
IOptions<InvitationEmailTextOptions> texts, |
||||
|
IEmailSender emailSender, |
||||
|
IEmailUrlGenerator emailUrlGenerator, |
||||
|
ISemanticLog log) |
||||
|
{ |
||||
|
Guard.NotNull(texts, nameof(texts)); |
||||
|
Guard.NotNull(emailSender, nameof(emailSender)); |
||||
|
Guard.NotNull(emailUrlGenerator, nameof(emailUrlGenerator)); |
||||
|
Guard.NotNull(log, nameof(log)); |
||||
|
|
||||
|
this.texts = texts.Value; |
||||
|
this.emailSender = emailSender; |
||||
|
this.emailUrlGenerator = emailUrlGenerator; |
||||
|
this.log = log; |
||||
|
} |
||||
|
|
||||
|
public Task SendExistingUserEmailAsync(IUser assigner, IUser assignee, string appName) |
||||
|
{ |
||||
|
return SendEmailAsync(texts.ExistingUserSubject, texts.ExistingUserBody, assigner, assignee, appName); |
||||
|
} |
||||
|
|
||||
|
public Task SendNewUserEmailAsync(IUser assigner, IUser assignee, string appName) |
||||
|
{ |
||||
|
return SendEmailAsync(texts.NewUserSubject, texts.NewUserBody, assigner, assignee, appName); |
||||
|
} |
||||
|
|
||||
|
private async Task SendEmailAsync(string emailSubj, string emailBody, IUser assigner, IUser assignee, string appName) |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(emailBody)) |
||||
|
{ |
||||
|
LogWarning("No email subject configured for new users"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (string.IsNullOrWhiteSpace(emailSubj)) |
||||
|
{ |
||||
|
LogWarning("No email body configured for new users"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var appUrl = emailUrlGenerator.GenerateUIUrl(); |
||||
|
|
||||
|
emailSubj = Format(emailSubj, assigner, assignee, appUrl, appName); |
||||
|
emailBody = Format(emailBody, assigner, assignee, appUrl, appName); |
||||
|
|
||||
|
await emailSender.SendAsync(assignee.Email, emailSubj, emailBody); |
||||
|
} |
||||
|
|
||||
|
private void LogWarning(string reason) |
||||
|
{ |
||||
|
log.LogWarning(w => w |
||||
|
.WriteProperty("action", "InviteUser") |
||||
|
.WriteProperty("status", "Failed") |
||||
|
.WriteProperty("reason", reason)); |
||||
|
} |
||||
|
|
||||
|
private string Format(string text, IUser assigner, IUser assignee, string uiUrl, string appName) |
||||
|
{ |
||||
|
text = text.Replace("$APP_NAME", appName); |
||||
|
|
||||
|
if (assigner != null) |
||||
|
{ |
||||
|
text = text.Replace("$ASSIGNER_EMAIL", assigner.Email); |
||||
|
text = text.Replace("$ASSIGNER_NAME", assigner.DisplayName()); |
||||
|
} |
||||
|
|
||||
|
if (assignee != null) |
||||
|
{ |
||||
|
text = text.Replace("$ASSIGNEE_EMAIL", assignee.Email); |
||||
|
text = text.Replace("$ASSIGNEE_NAME", assignee.DisplayName()); |
||||
|
} |
||||
|
|
||||
|
text = text.Replace("$UI_URL", uiUrl); |
||||
|
|
||||
|
return text; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Invitation |
||||
|
{ |
||||
|
public sealed class InvitationEmailTextOptions |
||||
|
{ |
||||
|
public string NewUserSubject { get; set; } |
||||
|
|
||||
|
public string NewUserBody { get; set; } |
||||
|
|
||||
|
public string ExistingUserSubject { get; set; } |
||||
|
|
||||
|
public string ExistingUserBody { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Infrastructure.Tasks; |
||||
|
using Squidex.Shared.Users; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Invitation |
||||
|
{ |
||||
|
public sealed class NoopInvitationEmailSender : IInvitationEmailSender |
||||
|
{ |
||||
|
public bool IsActive |
||||
|
{ |
||||
|
get { return false; } |
||||
|
} |
||||
|
|
||||
|
public Task SendExistingUserEmailAsync(IUser assigner, IUser assignee, string appName) |
||||
|
{ |
||||
|
return TaskHelper.Done; |
||||
|
} |
||||
|
|
||||
|
public Task SendNewUserEmailAsync(IUser assigner, IUser assignee, string appName) |
||||
|
{ |
||||
|
return TaskHelper.Done; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities |
||||
|
{ |
||||
|
public interface IEmailUrlGenerator |
||||
|
{ |
||||
|
string GenerateUIUrl(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Email |
||||
|
{ |
||||
|
public interface IEmailSender |
||||
|
{ |
||||
|
Task SendAsync(string recipient, string subject, string body); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Infrastructure.Email |
||||
|
{ |
||||
|
public sealed class SmptOptions |
||||
|
{ |
||||
|
public string Server { get; set; } |
||||
|
|
||||
|
public string Sender { get; set; } |
||||
|
|
||||
|
public string Username { get; set; } |
||||
|
|
||||
|
public string Password { get; set; } |
||||
|
|
||||
|
public bool EnableSsl { get; set; } |
||||
|
|
||||
|
public int Port { get; set; } = 587; |
||||
|
|
||||
|
public bool IsConfigured() |
||||
|
{ |
||||
|
return |
||||
|
!string.IsNullOrWhiteSpace(Server) && |
||||
|
!string.IsNullOrWhiteSpace(Sender) && |
||||
|
!string.IsNullOrWhiteSpace(Username) && |
||||
|
!string.IsNullOrWhiteSpace(Password); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Net; |
||||
|
using System.Net.Mail; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Email |
||||
|
{ |
||||
|
public sealed class SmtpEmailSender : IEmailSender |
||||
|
{ |
||||
|
private readonly SmtpClient smtpClient; |
||||
|
private readonly string sender; |
||||
|
|
||||
|
public SmtpEmailSender(IOptions<SmptOptions> options) |
||||
|
{ |
||||
|
Guard.NotNull(options, nameof(options)); |
||||
|
|
||||
|
var config = options.Value; |
||||
|
|
||||
|
smtpClient = new SmtpClient(config.Server, config.Port) |
||||
|
{ |
||||
|
Credentials = new NetworkCredential( |
||||
|
config.Username, |
||||
|
config.Password), |
||||
|
EnableSsl = config.EnableSsl |
||||
|
}; |
||||
|
|
||||
|
sender = config.Sender; |
||||
|
} |
||||
|
|
||||
|
public Task SendAsync(string recipient, string subject, string body) |
||||
|
{ |
||||
|
return smtpClient.SendMailAsync(sender, recipient, subject, body); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,160 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using FakeItEasy; |
||||
|
using Squidex.Domain.Apps.Events.Apps; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
using Squidex.Infrastructure.Log; |
||||
|
using Squidex.Shared.Users; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Invitation |
||||
|
{ |
||||
|
public class InvitationEmailEventConsumerTests |
||||
|
{ |
||||
|
private readonly IInvitationEmailSender emailSender = A.Fake<IInvitationEmailSender>(); |
||||
|
private readonly IUserResolver userResolver = A.Fake<IUserResolver>(); |
||||
|
private readonly IUser assigner = A.Fake<IUser>(); |
||||
|
private readonly IUser assignee = A.Fake<IUser>(); |
||||
|
private readonly ISemanticLog log = A.Fake<ISemanticLog>(); |
||||
|
private readonly string assignerId = Guid.NewGuid().ToString(); |
||||
|
private readonly string assigneeId = Guid.NewGuid().ToString(); |
||||
|
private readonly string appName = "my-app"; |
||||
|
private readonly InvitationEmailEventConsumer sut; |
||||
|
|
||||
|
public InvitationEmailEventConsumerTests() |
||||
|
{ |
||||
|
A.CallTo(() => emailSender.IsActive) |
||||
|
.Returns(true); |
||||
|
|
||||
|
sut = new InvitationEmailEventConsumer(emailSender, userResolver, log); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_ignore_contributors_assigned_by_clients() |
||||
|
{ |
||||
|
var @event = Envelope.Create(CreateEvent(RefTokenType.Client, true)); |
||||
|
|
||||
|
await sut.On(@event); |
||||
|
|
||||
|
A.CallTo(() => userResolver.FindByIdOrEmailAsync(A<string>.Ignored)) |
||||
|
.MustNotHaveHappened(); |
||||
|
|
||||
|
A.CallTo(() => emailSender.SendNewUserEmailAsync(A<IUser>.Ignored, A<IUser>.Ignored, appName)) |
||||
|
.MustNotHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_send_email_if_sender_not_active() |
||||
|
{ |
||||
|
var @event = Envelope.Create(CreateEvent(RefTokenType.Subject, true)); |
||||
|
|
||||
|
A.CallTo(() => emailSender.IsActive) |
||||
|
.Returns(false); |
||||
|
|
||||
|
A.CallTo(() => userResolver.FindByIdOrEmailAsync(assignerId)) |
||||
|
.Returns(Task.FromResult<IUser>(null)); |
||||
|
|
||||
|
await sut.On(@event); |
||||
|
|
||||
|
A.CallTo(() => userResolver.FindByIdOrEmailAsync(A<string>.Ignored)) |
||||
|
.MustNotHaveHappened(); |
||||
|
|
||||
|
A.CallTo(() => emailSender.SendNewUserEmailAsync(A<IUser>.Ignored, A<IUser>.Ignored, appName)) |
||||
|
.MustNotHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_send_email_if_assigner_not_found() |
||||
|
{ |
||||
|
var @event = Envelope.Create(CreateEvent(RefTokenType.Subject, true)); |
||||
|
|
||||
|
A.CallTo(() => userResolver.FindByIdOrEmailAsync(assignerId)) |
||||
|
.Returns(Task.FromResult<IUser>(null)); |
||||
|
|
||||
|
await sut.On(@event); |
||||
|
|
||||
|
A.CallTo(() => emailSender.SendNewUserEmailAsync(A<IUser>.Ignored, A<IUser>.Ignored, appName)) |
||||
|
.MustNotHaveHappened(); |
||||
|
|
||||
|
MustLogWarning(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_send_email_if_assignee_not_found() |
||||
|
{ |
||||
|
var @event = Envelope.Create(CreateEvent(RefTokenType.Subject, true)); |
||||
|
|
||||
|
A.CallTo(() => userResolver.FindByIdOrEmailAsync(assignerId)) |
||||
|
.Returns(assigner); |
||||
|
|
||||
|
A.CallTo(() => userResolver.FindByIdOrEmailAsync(assigneeId)) |
||||
|
.Returns(Task.FromResult<IUser>(null)); |
||||
|
|
||||
|
await sut.On(@event); |
||||
|
|
||||
|
A.CallTo(() => emailSender.SendNewUserEmailAsync(A<IUser>.Ignored, A<IUser>.Ignored, appName)) |
||||
|
.MustNotHaveHappened(); |
||||
|
|
||||
|
MustLogWarning(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_send_email_for_new_user() |
||||
|
{ |
||||
|
var @event = Envelope.Create(CreateEvent(RefTokenType.Subject, true)); |
||||
|
|
||||
|
A.CallTo(() => userResolver.FindByIdOrEmailAsync(assignerId)) |
||||
|
.Returns(assigner); |
||||
|
|
||||
|
A.CallTo(() => userResolver.FindByIdOrEmailAsync(assigneeId)) |
||||
|
.Returns(assignee); |
||||
|
|
||||
|
await sut.On(@event); |
||||
|
|
||||
|
A.CallTo(() => emailSender.SendNewUserEmailAsync(assigner, assignee, appName)) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_send_email_for_existing_user() |
||||
|
{ |
||||
|
var @event = Envelope.Create(CreateEvent(RefTokenType.Subject, false)); |
||||
|
|
||||
|
A.CallTo(() => userResolver.FindByIdOrEmailAsync(assignerId)) |
||||
|
.Returns(assigner); |
||||
|
|
||||
|
A.CallTo(() => userResolver.FindByIdOrEmailAsync(assigneeId)) |
||||
|
.Returns(assignee); |
||||
|
|
||||
|
await sut.On(@event); |
||||
|
|
||||
|
A.CallTo(() => emailSender.SendExistingUserEmailAsync(assigner, assignee, appName)) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
private void MustLogWarning() |
||||
|
{ |
||||
|
A.CallTo(() => log.Log(SemanticLogLevel.Warning, A<None>.Ignored, A<Action<None, IObjectWriter>>.Ignored)) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
private IEvent CreateEvent(string assignerType, bool isNew) |
||||
|
{ |
||||
|
return new AppContributorAssigned |
||||
|
{ |
||||
|
Actor = new RefToken(assignerType, assignerId), |
||||
|
AppId = new NamedId<Guid>(Guid.NewGuid(), appName), |
||||
|
ContributorId = assigneeId, |
||||
|
IsCreated = isNew |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,140 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Security.Claims; |
||||
|
using System.Threading.Tasks; |
||||
|
using FakeItEasy; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Email; |
||||
|
using Squidex.Infrastructure.Log; |
||||
|
using Squidex.Shared.Identity; |
||||
|
using Squidex.Shared.Users; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Invitation |
||||
|
{ |
||||
|
public class InvitationEmailSenderTests |
||||
|
{ |
||||
|
private readonly IEmailSender emailSender = A.Fake<IEmailSender>(); |
||||
|
private readonly IEmailUrlGenerator emailUrlGenerator = A.Fake<IEmailUrlGenerator>(); |
||||
|
private readonly IUser assigner = A.Fake<IUser>(); |
||||
|
private readonly IUser assignee = A.Fake<IUser>(); |
||||
|
private readonly ISemanticLog log = A.Fake<ISemanticLog>(); |
||||
|
private readonly string appName = "my-app"; |
||||
|
private readonly string uiUrl = "my-ui"; |
||||
|
private readonly InvitationEmailTextOptions texts = new InvitationEmailTextOptions(); |
||||
|
private readonly InvitationEmailSender sut; |
||||
|
|
||||
|
public InvitationEmailSenderTests() |
||||
|
{ |
||||
|
A.CallTo(() => assigner.Email) |
||||
|
.Returns("sebastian@squidex.io"); |
||||
|
A.CallTo(() => assigner.Claims) |
||||
|
.Returns(new List<Claim> { new Claim(SquidexClaimTypes.DisplayName, "Sebastian Stehle") }); |
||||
|
|
||||
|
A.CallTo(() => assignee.Email) |
||||
|
.Returns("qaisar@squidex.io"); |
||||
|
A.CallTo(() => assignee.Claims) |
||||
|
.Returns(new List<Claim> { new Claim(SquidexClaimTypes.DisplayName, "Qaisar Ahmad") }); |
||||
|
|
||||
|
A.CallTo(() => emailUrlGenerator.GenerateUIUrl()) |
||||
|
.Returns(uiUrl); |
||||
|
|
||||
|
sut = new InvitationEmailSender(Options.Create(texts), emailSender, emailUrlGenerator, log); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_format_assigner_email_and_send_email() |
||||
|
{ |
||||
|
await TestFormattingAsync("Email: $ASSIGNER_EMAIL", "Email: sebastian@squidex.io"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_format_assignee_email_and_send_email() |
||||
|
{ |
||||
|
await TestFormattingAsync("Email: $ASSIGNEE_EMAIL", "Email: qaisar@squidex.io"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_format_assigner_name_and_send_email() |
||||
|
{ |
||||
|
await TestFormattingAsync("Name: $ASSIGNER_NAME", "Name: Sebastian Stehle"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_format_assignee_name_and_send_email() |
||||
|
{ |
||||
|
await TestFormattingAsync("Name: $ASSIGNEE_NAME", "Name: Qaisar Ahmad"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_format_app_name_and_send_email() |
||||
|
{ |
||||
|
await TestFormattingAsync("App: $APP_NAME", "App: my-app"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_format_ui_url_and_send_email() |
||||
|
{ |
||||
|
await TestFormattingAsync("UI: $UI_URL", "UI: my-ui"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_send_email_if_texts_for_new_user_are_empty() |
||||
|
{ |
||||
|
await sut.SendNewUserEmailAsync(assigner, assignee, appName); |
||||
|
|
||||
|
A.CallTo(() => emailSender.SendAsync(assignee.Email, A<string>.Ignored, A<string>.Ignored)) |
||||
|
.MustNotHaveHappened(); |
||||
|
|
||||
|
MustLogWarning(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_send_email_if_texts_for_existing_user_are_empty() |
||||
|
{ |
||||
|
await sut.SendExistingUserEmailAsync(assigner, assignee, appName); |
||||
|
|
||||
|
A.CallTo(() => emailSender.SendAsync(assignee.Email, A<string>.Ignored, A<string>.Ignored)) |
||||
|
.MustNotHaveHappened(); |
||||
|
|
||||
|
MustLogWarning(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_send_email_for_existing_user() |
||||
|
{ |
||||
|
texts.ExistingUserSubject = "email-subject"; |
||||
|
texts.ExistingUserBody = "email-body"; |
||||
|
|
||||
|
await sut.SendExistingUserEmailAsync(assigner, assignee, appName); |
||||
|
|
||||
|
A.CallTo(() => emailSender.SendAsync(assignee.Email, "email-subject", "email-body")) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
private async Task TestFormattingAsync(string pattern, string result) |
||||
|
{ |
||||
|
texts.NewUserSubject = pattern; |
||||
|
texts.NewUserBody = pattern; |
||||
|
|
||||
|
await sut.SendNewUserEmailAsync(assigner, assignee, appName); |
||||
|
|
||||
|
A.CallTo(() => emailSender.SendAsync(assignee.Email, result, result)) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
private void MustLogWarning() |
||||
|
{ |
||||
|
A.CallTo(() => log.Log(SemanticLogLevel.Warning, A<None>.Ignored, A<Action<None, IObjectWriter>>.Ignored)) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue