diff --git a/EstateManagement.BusinessLogic.Tests/EstateManagement.BusinessLogic.Tests.csproj b/EstateManagement.BusinessLogic.Tests/EstateManagement.BusinessLogic.Tests.csproj index 001f7b58..ea3e458a 100644 --- a/EstateManagement.BusinessLogic.Tests/EstateManagement.BusinessLogic.Tests.csproj +++ b/EstateManagement.BusinessLogic.Tests/EstateManagement.BusinessLogic.Tests.csproj @@ -11,7 +11,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/EstateManagement.BusinessLogic.Tests/RequestHandler/MerchantRequestHandlerTests.cs b/EstateManagement.BusinessLogic.Tests/RequestHandler/MerchantRequestHandlerTests.cs index b23d5d6b..313a2c84 100644 --- a/EstateManagement.BusinessLogic.Tests/RequestHandler/MerchantRequestHandlerTests.cs +++ b/EstateManagement.BusinessLogic.Tests/RequestHandler/MerchantRequestHandlerTests.cs @@ -41,5 +41,20 @@ public void MerchantRequestHandler_AssignOperatorToMerchantRequest_IsHandled() }); } + + [Fact] + public void MerchantRequestHandler_CreateMerchantUserRequest_IsHandled() + { + Mock merchantDomainService = new Mock(); + MerchantRequestHandler handler = new MerchantRequestHandler(merchantDomainService.Object); + + CreateMerchantUserRequest request = TestData.CreateMerchantUserRequest; + + Should.NotThrow(async () => + { + await handler.Handle(request, CancellationToken.None); + }); + + } } } \ No newline at end of file diff --git a/EstateManagement.BusinessLogic.Tests/Requests/RequestsTests.cs b/EstateManagement.BusinessLogic.Tests/Requests/RequestsTests.cs index e2d879e7..3cd84ea4 100644 --- a/EstateManagement.BusinessLogic.Tests/Requests/RequestsTests.cs +++ b/EstateManagement.BusinessLogic.Tests/Requests/RequestsTests.cs @@ -103,5 +103,27 @@ public void CreateEstateUserRequest_CanBeCreated_IsCreated() createEstateUserRequest.FamilyName.ShouldBe(TestData.EstateUserFamilyName); } + + [Fact] + public void CreateMerchantUserRequest_CanBeCreated_IsCreated() + { + CreateMerchantUserRequest createMerchantUserRequest = CreateMerchantUserRequest.Create(TestData.EstateId, + TestData.MerchantId, + TestData.EstateUserEmailAddress, + TestData.EstateUserPassword, + TestData.EstateUserGivenName, + TestData.EstateUserMiddleName, + TestData.EstateUserFamilyName); + + createMerchantUserRequest.ShouldNotBeNull(); + createMerchantUserRequest.EstateId.ShouldBe(TestData.EstateId); + createMerchantUserRequest.MerchantId.ShouldBe(TestData.MerchantId); + createMerchantUserRequest.EmailAddress.ShouldBe(TestData.EstateUserEmailAddress); + createMerchantUserRequest.Password.ShouldBe(TestData.EstateUserPassword); + createMerchantUserRequest.GivenName.ShouldBe(TestData.EstateUserGivenName); + createMerchantUserRequest.MiddleName.ShouldBe(TestData.EstateUserMiddleName); + createMerchantUserRequest.FamilyName.ShouldBe(TestData.EstateUserFamilyName); + + } } } diff --git a/EstateManagement.BusinessLogic.Tests/Services/MerchantDomainServiceTests.cs b/EstateManagement.BusinessLogic.Tests/Services/MerchantDomainServiceTests.cs index 8c01805c..bc8c045c 100644 --- a/EstateManagement.BusinessLogic.Tests/Services/MerchantDomainServiceTests.cs +++ b/EstateManagement.BusinessLogic.Tests/Services/MerchantDomainServiceTests.cs @@ -10,6 +10,8 @@ namespace EstateManagement.BusinessLogic.Tests.Services using EstateAggregate; using MerchantAggregate; using Moq; + using SecurityService.Client; + using SecurityService.DataTransferObjects; using Shared.DomainDrivenDesign.EventStore; using Shared.EventStore.EventStore; using Shouldly; @@ -32,7 +34,9 @@ public async Task MerchantDomainService_CreateMerchant_MerchantIsCreated() aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(estateAggregateRepository.Object); aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(merchantAggregateRepository.Object); - MerchantDomainService domainService = new MerchantDomainService(aggregateRepositoryManager.Object); + Mock securityServiceClient = new Mock(); + + MerchantDomainService domainService = new MerchantDomainService(aggregateRepositoryManager.Object, securityServiceClient.Object); Should.NotThrow( async () => { @@ -70,7 +74,10 @@ public void MerchantDomainService_CreateMerchant_EstateNotFound_ErrorThrown() aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(estateAggregateRepository.Object); aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(merchantAggregateRepository.Object); - MerchantDomainService domainService = new MerchantDomainService(aggregateRepositoryManager.Object); + Mock securityServiceClient = new Mock(); + + MerchantDomainService domainService = new + MerchantDomainService(aggregateRepositoryManager.Object,securityServiceClient.Object); Should.Throw(async () => { @@ -108,7 +115,9 @@ public async Task MerchantDomainService_AssignOperatorToMerchant_OperatorAssigne aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(estateAggregateRepository.Object); aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(merchantAggregateRepository.Object); - MerchantDomainService domainService = new MerchantDomainService(aggregateRepositoryManager.Object); + Mock securityServiceClient = new Mock(); + + MerchantDomainService domainService = new MerchantDomainService(aggregateRepositoryManager.Object, securityServiceClient.Object); await domainService.AssignOperatorToMerchant(TestData.EstateId, TestData.MerchantId, @@ -132,7 +141,9 @@ public void MerchantDomainService_AssignOperatorToMerchant_MerchantNotCreated_Er aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(estateAggregateRepository.Object); aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(merchantAggregateRepository.Object); - MerchantDomainService domainService = new MerchantDomainService(aggregateRepositoryManager.Object); + Mock securityServiceClient = new Mock(); + + MerchantDomainService domainService = new MerchantDomainService(aggregateRepositoryManager.Object, securityServiceClient.Object); Should.Throw(async () => { @@ -159,7 +170,9 @@ public void MerchantDomainService_AssignOperatorToMerchant_EstateNotCreated_Erro aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(estateAggregateRepository.Object); aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(merchantAggregateRepository.Object); - MerchantDomainService domainService = new MerchantDomainService(aggregateRepositoryManager.Object); + Mock securityServiceClient = new Mock(); + + MerchantDomainService domainService = new MerchantDomainService(aggregateRepositoryManager.Object, securityServiceClient.Object); Should.Throw(async () => { @@ -186,7 +199,9 @@ public void MerchantDomainService_AssignOperatorToMerchant_OperatorNotFoundForEs aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(estateAggregateRepository.Object); aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(merchantAggregateRepository.Object); - MerchantDomainService domainService = new MerchantDomainService(aggregateRepositoryManager.Object); + Mock securityServiceClient = new Mock(); + + MerchantDomainService domainService = new MerchantDomainService(aggregateRepositoryManager.Object, securityServiceClient.Object); Should.Throw(async () => { @@ -215,7 +230,9 @@ public void MerchantDomainService_AssignOperatorToMerchant_OperatorRequiresMerch aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(estateAggregateRepository.Object); aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(merchantAggregateRepository.Object); - MerchantDomainService domainService = new MerchantDomainService(aggregateRepositoryManager.Object); + Mock securityServiceClient = new Mock(); + + MerchantDomainService domainService = new MerchantDomainService(aggregateRepositoryManager.Object, securityServiceClient.Object); Should.Throw(async () => { @@ -244,7 +261,9 @@ public void MerchantDomainService_AssignOperatorToMerchant_OperatorRequiresTermi aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(estateAggregateRepository.Object); aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(merchantAggregateRepository.Object); - MerchantDomainService domainService = new MerchantDomainService(aggregateRepositoryManager.Object); + Mock securityServiceClient = new Mock(); + + MerchantDomainService domainService = new MerchantDomainService(aggregateRepositoryManager.Object, securityServiceClient.Object); Should.Throw(async () => { @@ -256,5 +275,36 @@ await domainService.AssignOperatorToMerchant(TestData.EstateId, CancellationToken.None); }); } + + [Fact] + public async Task MerchantDomainService_CreateMerchantUser_MerchantUserIsCreated() + { + Mock> merchantAggregateRepository = new Mock>(); + merchantAggregateRepository.Setup(m => m.GetLatestVersion(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.CreatedMerchantAggregate); + merchantAggregateRepository.Setup(m => m.SaveChanges(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + + Mock aggregateRepositoryManager = new Mock(); + aggregateRepositoryManager.Setup(x => x.GetAggregateRepository(It.IsAny())).Returns(merchantAggregateRepository.Object); + + Mock securityServiceClient = new Mock(); + securityServiceClient.Setup(s => s.CreateUser(It.IsAny(), It.IsAny())).ReturnsAsync(new CreateUserResponse + { + UserId = Guid.NewGuid() + }); + + MerchantDomainService domainService = new MerchantDomainService(aggregateRepositoryManager.Object, securityServiceClient.Object); + + Should.NotThrow(async () => + { + await domainService.CreateMerchantUser(TestData.EstateId, + TestData.MerchantId, + TestData.MerchantUserEmailAddress, + TestData.MerchantUserPassword, + TestData.MerchantUserGivenName, + TestData.MerchantUserMiddleName, + TestData.MerchantUserFamilyName, + CancellationToken.None); + }); + } } } diff --git a/EstateManagement.BusinessLogic/RequestHandlers/MerchantRequestHandler.cs b/EstateManagement.BusinessLogic/RequestHandlers/MerchantRequestHandler.cs index 8e1e7f9b..d9b278a1 100644 --- a/EstateManagement.BusinessLogic/RequestHandlers/MerchantRequestHandler.cs +++ b/EstateManagement.BusinessLogic/RequestHandlers/MerchantRequestHandler.cs @@ -12,7 +12,9 @@ /// /// /// - public class MerchantRequestHandler : IRequestHandler, IRequestHandler + public class MerchantRequestHandler : IRequestHandler, + IRequestHandler, + IRequestHandler { #region Fields @@ -88,5 +90,20 @@ await this.MerchantDomainService.AssignOperatorToMerchant(request.EstateId, } #endregion + + public async Task Handle(CreateMerchantUserRequest request, + CancellationToken cancellationToken) + { + Guid userId = await this.MerchantDomainService.CreateMerchantUser(request.EstateId, + request.MerchantId, + request.EmailAddress, + request.Password, + request.GivenName, + request.MiddleName, + request.FamilyName, + cancellationToken); + + return userId; + } } } \ No newline at end of file diff --git a/EstateManagement.BusinessLogic/Requests/CreateMerchantUserRequest.cs b/EstateManagement.BusinessLogic/Requests/CreateMerchantUserRequest.cs new file mode 100644 index 00000000..7e8a7aa3 --- /dev/null +++ b/EstateManagement.BusinessLogic/Requests/CreateMerchantUserRequest.cs @@ -0,0 +1,125 @@ +namespace EstateManagement.BusinessLogic.Requests +{ + using System; + using MediatR; + + public class CreateMerchantUserRequest : IRequest + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The estate identifier. + /// The merchant identifier. + /// The email address. + /// The password. + /// Name of the given. + /// Name of the middle. + /// Name of the family. + private CreateMerchantUserRequest(Guid estateId, + Guid merchantId, + String emailAddress, + String password, + String givenName, + String middleName, + String familyName) + { + this.EstateId = estateId; + this.MerchantId = merchantId; + this.EmailAddress = emailAddress; + this.Password = password; + this.GivenName = givenName; + this.MiddleName = middleName; + this.FamilyName = familyName; + } + + #endregion + + #region Properties + + /// + /// Gets the estate identifier. + /// + /// + /// The estate identifier. + /// + public Guid EstateId { get; } + + /// + /// Gets the merchant identifier. + /// + /// + /// The merchant identifier. + /// + public Guid MerchantId { get; } + + /// + /// Gets the email address. + /// + /// + /// The email address. + /// + public String EmailAddress { get; } + + /// + /// Gets or sets the name of the family. + /// + /// + /// The name of the family. + /// + public String FamilyName { get; } + + /// + /// Gets or sets the name of the given. + /// + /// + /// The name of the given. + /// + public String GivenName { get; } + + /// + /// Gets or sets the name of the middle. + /// + /// + /// The name of the middle. + /// + public String MiddleName { get; } + + /// + /// Gets or sets the password. + /// + /// + /// The password. + /// + public String Password { get; } + + #endregion + + #region Methods + + /// + /// Creates the specified email address. + /// + /// The estate identifier. + /// The merchant identifier. + /// The email address. + /// The password. + /// Name of the given. + /// Name of the middle. + /// Name of the family. + /// + public static CreateMerchantUserRequest Create(Guid estateId, + Guid merchantId, + String emailAddress, + String password, + String givenName, + String middleName, + String familyName) + { + return new CreateMerchantUserRequest(estateId, merchantId, emailAddress, password, givenName, middleName, familyName); + } + + #endregion + } +} \ No newline at end of file diff --git a/EstateManagement.BusinessLogic/Services/EstateDomainService.cs b/EstateManagement.BusinessLogic/Services/EstateDomainService.cs index 8ea07fab..ed541c58 100644 --- a/EstateManagement.BusinessLogic/Services/EstateDomainService.cs +++ b/EstateManagement.BusinessLogic/Services/EstateDomainService.cs @@ -90,6 +90,17 @@ public async Task AddOperatorToEstate(Guid estateId, await estateAggregateRepository.SaveChanges(estateAggregate, cancellationToken); } + /// + /// Creates the estate user. + /// + /// The estate identifier. + /// The email address. + /// The password. + /// Name of the given. + /// Name of the middle. + /// Name of the family. + /// The cancellation token. + /// public async Task CreateEstateUser(Guid estateId, String emailAddress, String password, @@ -118,6 +129,11 @@ public async Task CreateEstateUser(Guid estateId, CreateUserResponse createUserResponse = await this.SecurityServiceClient.CreateUser(createUserRequest, cancellationToken); + // Add the user to the aggregate + estateAggregate.AddSecurityUser(createUserResponse.UserId, emailAddress); + + // TODO: add a delete user here in case the aggregate add fails... + await estateAggregateRepository.SaveChanges(estateAggregate, cancellationToken); return createUserResponse.UserId; diff --git a/EstateManagement.BusinessLogic/Services/IMerchantDomainService.cs b/EstateManagement.BusinessLogic/Services/IMerchantDomainService.cs index 9c5db4fb..1e5d2263 100644 --- a/EstateManagement.BusinessLogic/Services/IMerchantDomainService.cs +++ b/EstateManagement.BusinessLogic/Services/IMerchantDomainService.cs @@ -68,6 +68,27 @@ Task AssignOperatorToMerchant(Guid estateId, String terminalNumber, CancellationToken cancellationToken); + /// + /// Creates the merchant user. + /// + /// The estate identifier. + /// The merchant identifier. + /// The email address. + /// The password. + /// Name of the given. + /// Name of the middle. + /// Name of the family. + /// The cancellation token. + /// + Task CreateMerchantUser(Guid estateId, + Guid merchantId, + String emailAddress, + String password, + String givenName, + String middleName, + String familyName, + CancellationToken cancellationToken); + #endregion } } \ No newline at end of file diff --git a/EstateManagement.BusinessLogic/Services/MerchantDomainService.cs b/EstateManagement.BusinessLogic/Services/MerchantDomainService.cs index ff85816b..114187fd 100644 --- a/EstateManagement.BusinessLogic/Services/MerchantDomainService.cs +++ b/EstateManagement.BusinessLogic/Services/MerchantDomainService.cs @@ -1,12 +1,15 @@ namespace EstateManagement.BusinessLogic.Services { using System; + using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using EstateAggregate; using MerchantAggregate; using Models; + using SecurityService.Client; + using SecurityService.DataTransferObjects; using Shared.DomainDrivenDesign.EventStore; using Shared.EventStore.EventStore; @@ -23,6 +26,11 @@ public class MerchantDomainService : IMerchantDomainService /// private readonly IAggregateRepositoryManager AggregateRepositoryManager; + /// + /// The security service client + /// + private readonly ISecurityServiceClient SecurityServiceClient; + #endregion #region Constructors @@ -31,9 +39,11 @@ public class MerchantDomainService : IMerchantDomainService /// Initializes a new instance of the class. /// /// The aggregate repository manager. - public MerchantDomainService(IAggregateRepositoryManager aggregateRepositoryManager) + public MerchantDomainService(IAggregateRepositoryManager aggregateRepositoryManager, + ISecurityServiceClient securityServiceClient) { this.AggregateRepositoryManager = aggregateRepositoryManager; + this.SecurityServiceClient = securityServiceClient; } #endregion @@ -163,7 +173,45 @@ public async Task AssignOperatorToMerchant(Guid estateId, await merchantAggregateRepository.SaveChanges(merchantAggregate, cancellationToken); } + public async Task CreateMerchantUser(Guid estateId, + Guid merchantId, + String emailAddress, + String password, + String givenName, + String middleName, + String familyName, + CancellationToken cancellationToken) + { + IAggregateRepository merchantAggregateRepository = this.AggregateRepositoryManager.GetAggregateRepository(estateId); + MerchantAggregate merchantAggregate = await merchantAggregateRepository.GetLatestVersion(merchantId, cancellationToken); + + CreateUserRequest createUserRequest = new CreateUserRequest + { + EmailAddress = emailAddress, + FamilyName = familyName, + GivenName = givenName, + MiddleName = middleName, + Password = password, + PhoneNumber = "123456", // Is this really needed :| + Roles = new List(), + Claims = new Dictionary() + }; + + //createUserRequest.Roles.Add("Estate"); + createUserRequest.Claims.Add("EstateId", estateId.ToString()); + createUserRequest.Claims.Add("MerchantId", merchantId.ToString()); + + CreateUserResponse createUserResponse = await this.SecurityServiceClient.CreateUser(createUserRequest, cancellationToken); + + // Add the user to the aggregate + merchantAggregate.AddSecurityUser(createUserResponse.UserId, emailAddress); + // TODO: add a delete user here in case the aggregate add fails... + + await merchantAggregateRepository.SaveChanges(merchantAggregate, cancellationToken); + + return createUserResponse.UserId; + } #endregion } } \ No newline at end of file diff --git a/EstateManagement.Client/EstateClient.cs b/EstateManagement.Client/EstateClient.cs index 2740362a..a2cd0ac2 100644 --- a/EstateManagement.Client/EstateClient.cs +++ b/EstateManagement.Client/EstateClient.cs @@ -13,6 +13,7 @@ /// /// /// + /// /// /// public class EstateClient : ClientProxyBase, IEstateClient @@ -47,25 +48,27 @@ public EstateClient(Func baseAddressResolver, #region Methods /// - /// Adds the operator. + /// Assigns the operator to merchant. /// /// The access token. /// The estate identifier. - /// The create operator request. + /// The merchant identifier. + /// The assign operator request. /// The cancellation token. /// - public async Task CreateOperator(String accessToken, - Guid estateId, - CreateOperatorRequest createOperatorRequest, - CancellationToken cancellationToken) + public async Task AssignOperatorToMerchant(String accessToken, + Guid estateId, + Guid merchantId, + AssignOperatorRequest assignOperatorRequest, + CancellationToken cancellationToken) { - CreateOperatorResponse response = null; + AssignOperatorResponse response = null; - String requestUri = $"{this.BaseAddress}/api/estates/{estateId}/operators"; + String requestUri = $"{this.BaseAddress}/api/estates/{estateId}/merchants/{merchantId}/operators"; try { - String requestSerialised = JsonConvert.SerializeObject(createOperatorRequest); + String requestSerialised = JsonConvert.SerializeObject(assignOperatorRequest); StringContent httpContent = new StringContent(requestSerialised, Encoding.UTF8, "application/json"); @@ -79,12 +82,13 @@ public async Task CreateOperator(String accessToken, String content = await this.HandleResponse(httpResponse, cancellationToken); // call was successful so now deserialise the body to the response object - response = JsonConvert.DeserializeObject(content); + response = JsonConvert.DeserializeObject(content); } - catch (Exception ex) + catch(Exception ex) { // An exception has occurred, add some additional information to the message - Exception exception = new Exception($"Error creating new operator {createOperatorRequest.Name} for estate {estateId}.", ex); + Exception exception = new Exception($"Error assigning operator Id {assignOperatorRequest.OperatorId} to merchant Id {merchantId} for estate {estateId}.", + ex); throw exception; } @@ -229,38 +233,47 @@ public async Task CreateMerchant(String accessToken, } /// - /// Gets the estate. + /// Creates the merchant user. /// /// The access token. /// The estate identifier. + /// The merchant identifier. + /// The create merchant user request. /// The cancellation token. /// - public async Task GetEstate(String accessToken, - Guid estateId, - CancellationToken cancellationToken) + public async Task CreateMerchantUser(String accessToken, + Guid estateId, + Guid merchantId, + CreateMerchantUserRequest createMerchantUserRequest, + CancellationToken cancellationToken) { - EstateResponse response = null; + CreateMerchantUserResponse response = null; - String requestUri = $"{this.BaseAddress}/api/estates/{estateId}"; + String requestUri = $"{this.BaseAddress}/api/estates/{estateId}/merchants/{merchantId}/users"; try { + String requestSerialised = JsonConvert.SerializeObject(createMerchantUserRequest); + + StringContent httpContent = new StringContent(requestSerialised, Encoding.UTF8, "application/json"); + // Add the access token to the client headers //this.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); // Make the Http Call here - HttpResponseMessage httpResponse = await this.HttpClient.GetAsync(requestUri, cancellationToken); + HttpResponseMessage httpResponse = await this.HttpClient.PostAsync(requestUri, httpContent, cancellationToken); // Process the response String content = await this.HandleResponse(httpResponse, cancellationToken); // call was successful so now deserialise the body to the response object - response = JsonConvert.DeserializeObject(content); + response = JsonConvert.DeserializeObject(content); } catch(Exception ex) { // An exception has occurred, add some additional information to the message - Exception exception = new Exception($"Error getting estate Id {estateId}.", ex); + Exception exception = new Exception($"Error creating new mercant user Merchant Id {estateId} Email Address {createMerchantUserRequest.EmailAddress}.", + ex); throw exception; } @@ -269,21 +282,65 @@ public async Task GetEstate(String accessToken, } /// - /// Gets the merchant. + /// Adds the operator. /// /// The access token. /// The estate identifier. - /// The merchant identifier. + /// The create operator request. /// The cancellation token. /// - public async Task GetMerchant(String accessToken, + public async Task CreateOperator(String accessToken, + Guid estateId, + CreateOperatorRequest createOperatorRequest, + CancellationToken cancellationToken) + { + CreateOperatorResponse response = null; + + String requestUri = $"{this.BaseAddress}/api/estates/{estateId}/operators"; + + try + { + String requestSerialised = JsonConvert.SerializeObject(createOperatorRequest); + + StringContent httpContent = new StringContent(requestSerialised, Encoding.UTF8, "application/json"); + + // Add the access token to the client headers + //this.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + // Make the Http Call here + HttpResponseMessage httpResponse = await this.HttpClient.PostAsync(requestUri, httpContent, cancellationToken); + + // Process the response + String content = await this.HandleResponse(httpResponse, cancellationToken); + + // call was successful so now deserialise the body to the response object + response = JsonConvert.DeserializeObject(content); + } + catch(Exception ex) + { + // An exception has occurred, add some additional information to the message + Exception exception = new Exception($"Error creating new operator {createOperatorRequest.Name} for estate {estateId}.", ex); + + throw exception; + } + + return response; + } + + /// + /// Gets the estate. + /// + /// The access token. + /// The estate identifier. + /// The cancellation token. + /// + public async Task GetEstate(String accessToken, Guid estateId, - Guid merchantId, CancellationToken cancellationToken) { - MerchantResponse response = null; + EstateResponse response = null; - String requestUri = $"{this.BaseAddress}/api/estates/{estateId}/merchants/{merchantId}"; + String requestUri = $"{this.BaseAddress}/api/estates/{estateId}"; try { @@ -297,12 +354,12 @@ public async Task GetMerchant(String accessToken, String content = await this.HandleResponse(httpResponse, cancellationToken); // call was successful so now deserialise the body to the response object - response = JsonConvert.DeserializeObject(content); + response = JsonConvert.DeserializeObject(content); } - catch (Exception ex) + catch(Exception ex) { // An exception has occurred, add some additional information to the message - Exception exception = new Exception($"Error getting merchant Id {merchantId} in estate {estateId}.", ex); + Exception exception = new Exception($"Error getting estate Id {estateId}.", ex); throw exception; } @@ -311,46 +368,40 @@ public async Task GetMerchant(String accessToken, } /// - /// Assigns the operator to merchant. + /// Gets the merchant. /// /// The access token. /// The estate identifier. /// The merchant identifier. - /// The assign operator request. /// The cancellation token. /// - public async Task AssignOperatorToMerchant(String accessToken, - Guid estateId, - Guid merchantId, - AssignOperatorRequest assignOperatorRequest, - CancellationToken cancellationToken) + public async Task GetMerchant(String accessToken, + Guid estateId, + Guid merchantId, + CancellationToken cancellationToken) { - AssignOperatorResponse response = null; + MerchantResponse response = null; - String requestUri = $"{this.BaseAddress}/api/estates/{estateId}/merchants/{merchantId}/operators"; + String requestUri = $"{this.BaseAddress}/api/estates/{estateId}/merchants/{merchantId}"; try { - String requestSerialised = JsonConvert.SerializeObject(assignOperatorRequest); - - StringContent httpContent = new StringContent(requestSerialised, Encoding.UTF8, "application/json"); - // Add the access token to the client headers //this.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); // Make the Http Call here - HttpResponseMessage httpResponse = await this.HttpClient.PostAsync(requestUri, httpContent, cancellationToken); + HttpResponseMessage httpResponse = await this.HttpClient.GetAsync(requestUri, cancellationToken); // Process the response String content = await this.HandleResponse(httpResponse, cancellationToken); // call was successful so now deserialise the body to the response object - response = JsonConvert.DeserializeObject(content); + response = JsonConvert.DeserializeObject(content); } - catch (Exception ex) + catch(Exception ex) { // An exception has occurred, add some additional information to the message - Exception exception = new Exception($"Error assigning operator Id {assignOperatorRequest.OperatorId} to merchant Id {merchantId} for estate {estateId}.", ex); + Exception exception = new Exception($"Error getting merchant Id {merchantId} in estate {estateId}.", ex); throw exception; } diff --git a/EstateManagement.Client/IEstateClient.cs b/EstateManagement.Client/IEstateClient.cs index 368f767c..45e6e62d 100644 --- a/EstateManagement.Client/IEstateClient.cs +++ b/EstateManagement.Client/IEstateClient.cs @@ -45,7 +45,7 @@ Task CreateEstate(String accessToken, /// The access token. /// The estate identifier. /// The create estate user request. - /// The none. + /// The cancellation token. /// Task CreateEstateUser(String accessToken, Guid estateId, @@ -65,6 +65,21 @@ Task CreateMerchant(String accessToken, CreateMerchantRequest createMerchantRequest, CancellationToken cancellationToken); + /// + /// Creates the merchant user. + /// + /// The access token. + /// The estate identifier. + /// The merchant identifier. + /// The create merchant user request. + /// The cancellation token. + /// + Task CreateMerchantUser(String accessToken, + Guid estateId, + Guid merchantId, + CreateMerchantUserRequest createMerchantUserRequest, + CancellationToken cancellationToken); + /// /// Adds the operator. /// diff --git a/EstateManagement.DataTransferObjects/Requests/CreateMerchantUserRequest.cs b/EstateManagement.DataTransferObjects/Requests/CreateMerchantUserRequest.cs new file mode 100644 index 00000000..848e7991 --- /dev/null +++ b/EstateManagement.DataTransferObjects/Requests/CreateMerchantUserRequest.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace EstateManagement.DataTransferObjects.Requests +{ + using System.ComponentModel.DataAnnotations; + using System.Diagnostics.CodeAnalysis; + using Newtonsoft.Json; + + [ExcludeFromCodeCoverage] + public class CreateMerchantUserRequest + { + [Required] + [JsonProperty("email_address")] + public String EmailAddress { get; set; } + + /// + /// Gets or sets the password. + /// + /// + /// The password. + /// + [Required] + [JsonProperty("password")] + public String Password { get; set; } + + /// + /// Gets or sets the name of the given. + /// + /// + /// The name of the given. + /// + [Required] + [JsonProperty("given_name")] + public String GivenName { get; set; } + + /// + /// Gets or sets the name of the middle. + /// + /// + /// The name of the middle. + /// + [JsonProperty("middle_name")] + public String MiddleName { get; set; } + + /// + /// Gets or sets the name of the family. + /// + /// + /// The name of the family. + /// + [Required] + [JsonProperty("family_name")] + public String FamilyName { get; set; } + } +} diff --git a/EstateManagement.DataTransferObjects/Responses/CreateMerchantUserResponse.cs b/EstateManagement.DataTransferObjects/Responses/CreateMerchantUserResponse.cs new file mode 100644 index 00000000..f2faa632 --- /dev/null +++ b/EstateManagement.DataTransferObjects/Responses/CreateMerchantUserResponse.cs @@ -0,0 +1,35 @@ +namespace EstateManagement.DataTransferObjects.Responses +{ + using System; + using Newtonsoft.Json; + + public class CreateMerchantUserResponse + { + /// + /// Gets or sets the name of the estate. + /// + /// + /// The name of the estate. + /// + [JsonProperty("estate_id")] + public Guid EstateId { get; set; } + + /// + /// Gets or sets the merchant identifier. + /// + /// + /// The merchant identifier. + /// + [JsonProperty("merchant_id")] + public Guid MerchantId { get; set; } + + /// + /// Gets or sets the user identifier. + /// + /// + /// The user identifier. + /// + [JsonProperty("user_id")] + public Guid UserId { get; set; } + } +} \ No newline at end of file diff --git a/EstateManagement.EstateAggregate.Tests/EstateManagement.EstateAggregate.Tests.csproj b/EstateManagement.EstateAggregate.Tests/EstateManagement.EstateAggregate.Tests.csproj index 9b8f234c..5e491b79 100644 --- a/EstateManagement.EstateAggregate.Tests/EstateManagement.EstateAggregate.Tests.csproj +++ b/EstateManagement.EstateAggregate.Tests/EstateManagement.EstateAggregate.Tests.csproj @@ -7,7 +7,7 @@ - + diff --git a/EstateManagement.IntegrationTests/EstateManagement.IntegrationTests.csproj b/EstateManagement.IntegrationTests/EstateManagement.IntegrationTests.csproj index a187c516..2b1b750e 100644 --- a/EstateManagement.IntegrationTests/EstateManagement.IntegrationTests.csproj +++ b/EstateManagement.IntegrationTests/EstateManagement.IntegrationTests.csproj @@ -10,7 +10,7 @@ - + diff --git a/EstateManagement.IntegrationTests/Merchant/Merchant.feature b/EstateManagement.IntegrationTests/Merchant/Merchant.feature index ea66f5ee..851963be 100644 --- a/EstateManagement.IntegrationTests/Merchant/Merchant.feature +++ b/EstateManagement.IntegrationTests/Merchant/Merchant.feature @@ -23,3 +23,12 @@ Scenario: Assign Operator To Merchant | OperatorName | MerchantName | MerchantNumber | TerminalNumber | | Test Operator 1 | Test Merchant 1 | 00000001 | 10000001 | +@PRTest +Scenario: Create Security User + Given I create the following merchants + | MerchantName | AddressLine1 | Town | Region | Country | ContactName | EmailAddress | EstateName | + | Test Merchant 1 | Address Line 1 | TestTown | Test Region | United Kingdom | Test Contact 1 | testcontact1@merchant1.co.uk | Test Estate 1 | + When I create the following security users + | EmailAddress | Password | GivenName | FamilyName | MerchantName | + | merchantuser1@testmerchant1.co.uk | 123456 | TestMerchant | User1 | Test Merchant 1 | + diff --git a/EstateManagement.IntegrationTests/Merchant/Merchant.feature.cs b/EstateManagement.IntegrationTests/Merchant/Merchant.feature.cs index 7506feb9..bddc900f 100644 --- a/EstateManagement.IntegrationTests/Merchant/Merchant.feature.cs +++ b/EstateManagement.IntegrationTests/Merchant/Merchant.feature.cs @@ -238,6 +238,79 @@ public virtual void AssignOperatorToMerchant() this.ScenarioCleanup(); } + [Xunit.SkippableFactAttribute(DisplayName="Create Security User")] + [Xunit.TraitAttribute("FeatureTitle", "Merchant")] + [Xunit.TraitAttribute("Description", "Create Security User")] + [Xunit.TraitAttribute("Category", "PRTest")] + public virtual void CreateSecurityUser() + { + string[] tagsOfScenario = new string[] { + "PRTest"}; + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Create Security User", null, new string[] { + "PRTest"}); +#line 27 +this.ScenarioInitialize(scenarioInfo); +#line hidden + bool isScenarioIgnored = default(bool); + bool isFeatureIgnored = default(bool); + if ((tagsOfScenario != null)) + { + isScenarioIgnored = tagsOfScenario.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); + } + if ((this._featureTags != null)) + { + isFeatureIgnored = this._featureTags.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); + } + if ((isScenarioIgnored || isFeatureIgnored)) + { + testRunner.SkipScenario(); + } + else + { + this.ScenarioStart(); +#line 4 +this.FeatureBackground(); +#line hidden + TechTalk.SpecFlow.Table table11 = new TechTalk.SpecFlow.Table(new string[] { + "MerchantName", + "AddressLine1", + "Town", + "Region", + "Country", + "ContactName", + "EmailAddress", + "EstateName"}); + table11.AddRow(new string[] { + "Test Merchant 1", + "Address Line 1", + "TestTown", + "Test Region", + "United Kingdom", + "Test Contact 1", + "testcontact1@merchant1.co.uk", + "Test Estate 1"}); +#line 28 + testRunner.Given("I create the following merchants", ((string)(null)), table11, "Given "); +#line hidden + TechTalk.SpecFlow.Table table12 = new TechTalk.SpecFlow.Table(new string[] { + "EmailAddress", + "Password", + "GivenName", + "FamilyName", + "MerchantName"}); + table12.AddRow(new string[] { + "merchantuser1@testmerchant1.co.uk", + "123456", + "TestMerchant", + "User1", + "Test Merchant 1"}); +#line 31 + testRunner.When("I create the following security users", ((string)(null)), table12, "When "); +#line hidden + } + this.ScenarioCleanup(); + } + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.1.0.0")] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class FixtureData : System.IDisposable diff --git a/EstateManagement.IntegrationTests/Shared/SharedSteps.cs b/EstateManagement.IntegrationTests/Shared/SharedSteps.cs index dab9d279..e6c6fde9 100644 --- a/EstateManagement.IntegrationTests/Shared/SharedSteps.cs +++ b/EstateManagement.IntegrationTests/Shared/SharedSteps.cs @@ -214,25 +214,60 @@ public async Task WhenICreateTheFollowingSecurityUsers(Table table) { foreach (TableRow tableRow in table.Rows) { - // lookup the estate id based on the name in the table - String estateName = SpecflowTableHelper.GetStringRowValue(tableRow, "EstateName"); - Guid estateId = this.TestingContext.Estates.Single(e => e.Key == estateName).Value; - - CreateEstateUserRequest createEstateUserRequest = new CreateEstateUserRequest - { - EmailAddress = SpecflowTableHelper.GetStringRowValue(tableRow, "EmailAddress"), - FamilyName = SpecflowTableHelper.GetStringRowValue(tableRow, "FamilyName"), - GivenName = SpecflowTableHelper.GetStringRowValue(tableRow, "GivenName"), - MiddleName = SpecflowTableHelper.GetStringRowValue(tableRow, "MiddleName"), - Password = SpecflowTableHelper.GetStringRowValue(tableRow, "Password") - }; + if (tableRow.ContainsKey("EstateName")) + { + // Creating an Estate User + + // lookup the estate id based on the name in the table + String estateName = SpecflowTableHelper.GetStringRowValue(tableRow, "EstateName"); + Guid estateId = this.TestingContext.Estates.Single(e => e.Key == estateName).Value; + + CreateEstateUserRequest createEstateUserRequest = new CreateEstateUserRequest + { + EmailAddress = SpecflowTableHelper.GetStringRowValue(tableRow, "EmailAddress"), + FamilyName = SpecflowTableHelper.GetStringRowValue(tableRow, "FamilyName"), + GivenName = SpecflowTableHelper.GetStringRowValue(tableRow, "GivenName"), + MiddleName = SpecflowTableHelper.GetStringRowValue(tableRow, "MiddleName"), + Password = SpecflowTableHelper.GetStringRowValue(tableRow, "Password") + }; + + CreateEstateUserResponse createEstateUserResponse = + await this.TestingContext.DockerHelper.EstateClient.CreateEstateUser(String.Empty, estateId, createEstateUserRequest, CancellationToken.None); - CreateEstateUserResponse createEstateUserResponse = await this.TestingContext.DockerHelper.EstateClient.CreateEstateUser(String.Empty, estateId, createEstateUserRequest, CancellationToken.None); + createEstateUserResponse.EstateId.ShouldBe(estateId); + createEstateUserResponse.UserId.ShouldNotBe(Guid.Empty); - createEstateUserResponse.EstateId.ShouldBe(estateId); - createEstateUserResponse.UserId.ShouldNotBe(Guid.Empty); + this.TestingContext.Logger.LogInformation($"Security user {createEstateUserRequest.EmailAddress} assigned to Estate {estateName}"); + } + else if (tableRow.ContainsKey("MerchantName")) + { + // Creating a merchant user + + // lookup the merchant id based on the name in the table + String merchantName = SpecflowTableHelper.GetStringRowValue(tableRow, "MerchantName"); + Guid merchantId = this.TestingContext.Merchants.Single(m => m.Key == merchantName).Value; + + // Now find the estate Id + Guid estateId = this.TestingContext.EstateMerchants.Single(e => e.Value.Contains(merchantId)).Key; - this.TestingContext.Logger.LogInformation($"Security user {createEstateUserRequest.EmailAddress} assigned to Estate {estateName}"); + CreateMerchantUserRequest createMerchantUserRequest = new CreateMerchantUserRequest + { + EmailAddress = SpecflowTableHelper.GetStringRowValue(tableRow, "EmailAddress"), + FamilyName = SpecflowTableHelper.GetStringRowValue(tableRow, "FamilyName"), + GivenName = SpecflowTableHelper.GetStringRowValue(tableRow, "GivenName"), + MiddleName = SpecflowTableHelper.GetStringRowValue(tableRow, "MiddleName"), + Password = SpecflowTableHelper.GetStringRowValue(tableRow, "Password") + }; + + CreateMerchantUserResponse createMerchantUserResponse = + await this.TestingContext.DockerHelper.EstateClient.CreateMerchantUser(String.Empty, estateId, merchantId, createMerchantUserRequest, CancellationToken.None); + + createMerchantUserResponse.EstateId.ShouldBe(estateId); + createMerchantUserResponse.MerchantId.ShouldBe(merchantId); + createMerchantUserResponse.UserId.ShouldNotBe(Guid.Empty); + + this.TestingContext.Logger.LogInformation($"Security user {createMerchantUserRequest.EmailAddress} assigned to Merchant {merchantName}"); + } } } diff --git a/EstateManagement.Merchant.DomainEvents/SecurityUserAddedEvent.cs b/EstateManagement.Merchant.DomainEvents/SecurityUserAddedEvent.cs new file mode 100644 index 00000000..b689002d --- /dev/null +++ b/EstateManagement.Merchant.DomainEvents/SecurityUserAddedEvent.cs @@ -0,0 +1,108 @@ +namespace EstateManagement.Merchant.DomainEvents +{ + using System; + using System.Diagnostics.CodeAnalysis; + using Newtonsoft.Json; + using Shared.DomainDrivenDesign.EventSourcing; + + /// + /// + /// + /// + [JsonObject] + public class SecurityUserAddedEvent : DomainEvent + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + [ExcludeFromCodeCoverage] + public SecurityUserAddedEvent() + { + //We need this for serialisation, so just embrace the DDD crime + } + + /// + /// Initializes a new instance of the class. + /// + /// The aggregate identifier. + /// The event identifier. + /// The estate identifier. + /// The security user identifier. + /// The email address. + private SecurityUserAddedEvent(Guid aggregateId, + Guid eventId, + Guid estateId, + Guid securityUserId, + String emailAddress) : base(aggregateId, eventId) + { + this.EstateId = estateId; + this.MerchantId = aggregateId; + this.SecurityUserId = securityUserId; + this.EmailAddress = emailAddress; + } + + #endregion + + #region Properties + + /// + /// Gets the email address. + /// + /// + /// The email address. + /// + [JsonProperty] + public String EmailAddress { get; private set; } + + /// + /// Gets the estate identifier. + /// + /// + /// The estate identifier. + /// + [JsonProperty] + public Guid EstateId { get; private set; } + + /// + /// Gets the merchant identifier. + /// + /// + /// The merchant identifier. + /// + [JsonProperty] + public Guid MerchantId { get; private set; } + + /// + /// Gets the security user identifier. + /// + /// + /// The security user identifier. + /// + [JsonProperty] + public Guid SecurityUserId { get; private set; } + + #endregion + + #region Methods + + /// + /// Creates the specified aggregate identifier. + /// + /// The aggregate identifier. + /// The estate identifier. + /// The security user identifier. + /// The email address. + /// + public static SecurityUserAddedEvent Create(Guid aggregateId, + Guid estateId, + Guid securityUserId, + String emailAddress) + { + return new SecurityUserAddedEvent(aggregateId, Guid.NewGuid(), estateId, securityUserId, emailAddress); + } + + #endregion + } +} \ No newline at end of file diff --git a/EstateManagement.MerchantAggregate.Tests/DomainEventTests.cs b/EstateManagement.MerchantAggregate.Tests/DomainEventTests.cs index 47a08e57..b53e03fc 100644 --- a/EstateManagement.MerchantAggregate.Tests/DomainEventTests.cs +++ b/EstateManagement.MerchantAggregate.Tests/DomainEventTests.cs @@ -93,7 +93,24 @@ public void OperatorAssignedToMerchantEvent_CanBeCreated_IsCreated() operatorAssignedToMerchantEvent.Name.ShouldBe(TestData.OperatorName); operatorAssignedToMerchantEvent.MerchantNumber.ShouldBe(TestData.OperatorMerchantNumber); operatorAssignedToMerchantEvent.TerminalNumber.ShouldBe(TestData.OperatorTerminalNumber); + } + + [Fact] + public void SecurityUserAddedEvent_CanBeCreated_IsCreated() + { + SecurityUserAddedEvent securityUserAddedEvent = SecurityUserAddedEvent.Create(TestData.MerchantId, + TestData.EstateId, + TestData.SecurityUserId, + TestData.EstateUserEmailAddress); + securityUserAddedEvent.ShouldNotBeNull(); + securityUserAddedEvent.AggregateId.ShouldBe(TestData.MerchantId); + securityUserAddedEvent.MerchantId.ShouldBe(TestData.MerchantId); + securityUserAddedEvent.EventCreatedDateTime.ShouldNotBe(DateTime.MinValue); + securityUserAddedEvent.EventId.ShouldNotBe(Guid.Empty); + securityUserAddedEvent.EstateId.ShouldBe(TestData.EstateId); + securityUserAddedEvent.SecurityUserId.ShouldBe(TestData.SecurityUserId); + securityUserAddedEvent.EmailAddress.ShouldBe(TestData.EstateUserEmailAddress); } } } diff --git a/EstateManagement.MerchantAggregate.Tests/EstateManagement.MerchantAggregate.Tests.csproj b/EstateManagement.MerchantAggregate.Tests/EstateManagement.MerchantAggregate.Tests.csproj index eb236f9c..3ac3bd77 100644 --- a/EstateManagement.MerchantAggregate.Tests/EstateManagement.MerchantAggregate.Tests.csproj +++ b/EstateManagement.MerchantAggregate.Tests/EstateManagement.MerchantAggregate.Tests.csproj @@ -7,7 +7,7 @@ - + diff --git a/EstateManagement.MerchantAggregate.Tests/MerchantAggregateTests.cs b/EstateManagement.MerchantAggregate.Tests/MerchantAggregateTests.cs index 705275f4..98e40cd3 100644 --- a/EstateManagement.MerchantAggregate.Tests/MerchantAggregateTests.cs +++ b/EstateManagement.MerchantAggregate.Tests/MerchantAggregateTests.cs @@ -159,5 +159,32 @@ public void MerchantAggregate_AssignOperator_OperatorAlreadyAssigned_ErrorThrown aggregate.AssignOperator(TestData.OperatorId, TestData.OperatorName, TestData.OperatorMerchantNumber, TestData.OperatorTerminalNumber); }); } + + [Fact] + public void MerchantAggregate_AddSecurityUserToMerchant_SecurityUserIsAdded() + { + MerchantAggregate aggregate = MerchantAggregate.Create(TestData.MerchantId); + aggregate.Create(TestData.EstateId, TestData.MerchantName, TestData.DateMerchantCreated); + aggregate.AddSecurityUser(TestData.SecurityUserId, TestData.MerchantUserEmailAddress); + + Merchant merchantModel = aggregate.GetMerchant(); + merchantModel.SecurityUsers.ShouldHaveSingleItem(); + SecurityUser securityUser = merchantModel.SecurityUsers.Single(); + securityUser.EmailAddress.ShouldBe(TestData.MerchantUserEmailAddress); + securityUser.SecurityUserId.ShouldBe(TestData.SecurityUserId); + } + + [Fact] + public void MerchantAggregate_AddSecurityUserToMerchant_MerchantNotCreated_ErrorThrown() + { + MerchantAggregate aggregate = MerchantAggregate.Create(TestData.MerchantId); + + InvalidOperationException exception = Should.Throw(() => + { + aggregate.AddSecurityUser(TestData.SecurityUserId, TestData.EstateUserEmailAddress); + }); + + exception.Message.ShouldContain("Merchant has not been created"); + } } } diff --git a/EstateManagement.MerchantAggregate/MerchantAggregate.cs b/EstateManagement.MerchantAggregate/MerchantAggregate.cs index 660f49fa..ef7fcc9c 100644 --- a/EstateManagement.MerchantAggregate/MerchantAggregate.cs +++ b/EstateManagement.MerchantAggregate/MerchantAggregate.cs @@ -33,6 +33,11 @@ public class MerchantAggregate : Aggregate /// private readonly List Operators; + /// + /// The security users + /// + private readonly List SecurityUsers; + #endregion #region Constructors @@ -47,6 +52,7 @@ public MerchantAggregate() this.Addresses = new List
(); this.Contacts = new List(); this.Operators = new List(); + this.SecurityUsers = new List(); } /// @@ -61,6 +67,7 @@ private MerchantAggregate(Guid aggregateId) this.Addresses = new List
(); this.Contacts = new List(); this.Operators = new List(); + this.SecurityUsers = new List(); } #endregion @@ -153,6 +160,15 @@ public Merchant GetMerchant() })); } + if (this.SecurityUsers.Any()) + { + this.SecurityUsers.ForEach(s => merchantModel.SecurityUsers.Add(new Models.Merchant.SecurityUser + { + SecurityUserId = s.SecurityUserId, + EmailAddress = s.EmailAddress + })); + } + return merchantModel; } @@ -372,5 +388,27 @@ private void PlayEvent(OperatorAssignedToMerchantEvent operatorAssignedToMerchan this.Operators.Add(@operator); } + + /// + /// Adds the security user. + /// + /// The security user identifier. + /// The email address. + public void AddSecurityUser(Guid securityUserId, + String emailAddress) + { + this.EnsureMerchantHasBeenCreated(); + + SecurityUserAddedEvent securityUserAddedEvent = SecurityUserAddedEvent.Create(this.AggregateId, this.EstateId, securityUserId, emailAddress); + + this.ApplyAndPend(securityUserAddedEvent); + } + + private void PlayEvent(SecurityUserAddedEvent domainEvent) + { + SecurityUser securityUser = SecurityUser.Create(domainEvent.SecurityUserId, domainEvent.EmailAddress); + + this.SecurityUsers.Add(securityUser); + } } } \ No newline at end of file diff --git a/EstateManagement.MerchantAggregate/SecurityUser.cs b/EstateManagement.MerchantAggregate/SecurityUser.cs new file mode 100644 index 00000000..ae56d13a --- /dev/null +++ b/EstateManagement.MerchantAggregate/SecurityUser.cs @@ -0,0 +1,47 @@ +namespace EstateManagement.MerchantAggregate +{ + using System; + + /// + /// + /// + internal class SecurityUser + { + /// + /// Gets the security user identifier. + /// + /// + /// The security user identifier. + /// + internal Guid SecurityUserId { get; } + /// + /// Gets the email address. + /// + /// + /// The email address. + /// + internal String EmailAddress { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The security user identifier. + /// The email address. + private SecurityUser(Guid securityUserId,String emailAddress) + { + this.SecurityUserId = securityUserId; + this.EmailAddress = emailAddress; + } + + /// + /// Creates the specified security user identifier. + /// + /// The security user identifier. + /// The email address. + /// + internal static SecurityUser Create(Guid securityUserId, String emailAddress) + { + return new SecurityUser(securityUserId, emailAddress); + } + } +} diff --git a/EstateManagement.Models/Merchant/Merchant.cs b/EstateManagement.Models/Merchant/Merchant.cs index 1ba8a0a0..3342ec9d 100644 --- a/EstateManagement.Models/Merchant/Merchant.cs +++ b/EstateManagement.Models/Merchant/Merchant.cs @@ -12,8 +12,12 @@ public class Merchant public Merchant() { this.Addresses = new List
(); + this.Contacts = new List(); - this.Operators=new List(); + + this.Operators = new List(); + + this.SecurityUsers = new List(); } #endregion @@ -68,6 +72,14 @@ public Merchant() /// public List Operators { get; set; } + /// + /// Gets or sets the security users. + /// + /// + /// The security users. + /// + public List SecurityUsers { get; set; } + #endregion } } \ No newline at end of file diff --git a/EstateManagement.Models/Merchant/SecurityUser.cs b/EstateManagement.Models/Merchant/SecurityUser.cs new file mode 100644 index 00000000..3b176d23 --- /dev/null +++ b/EstateManagement.Models/Merchant/SecurityUser.cs @@ -0,0 +1,32 @@ +namespace EstateManagement.Models.Merchant +{ + using System; + using System.Diagnostics.CodeAnalysis; + + /// + /// + /// + [ExcludeFromCodeCoverage] + public class SecurityUser + { + #region Properties + + /// + /// Gets or sets the email address. + /// + /// + /// The email address. + /// + public String EmailAddress { get; set; } + + /// + /// Gets or sets the security user identifier. + /// + /// + /// The security user identifier. + /// + public Guid SecurityUserId { get; set; } + + #endregion + } +} \ No newline at end of file diff --git a/EstateManagement.Testing/TestData.cs b/EstateManagement.Testing/TestData.cs index c2f6cb8c..cbaede93 100644 --- a/EstateManagement.Testing/TestData.cs +++ b/EstateManagement.Testing/TestData.cs @@ -254,13 +254,22 @@ public static MerchantAggregate MerchantAggregateWithOperator() public static String EstateUserEmailAddress = "testestateuser@estate1.co.uk"; + public static String MerchantUserEmailAddress = "testmerchantuser@merchant1.co.uk"; + public static String EstateUserPassword="123456"; + public static String MerchantUserPassword = "123456"; + public static String EstateUserGivenName = "Test"; + public static String MerchantUserGivenName = "Test"; + public static String EstateUserMiddleName = "Middle"; + public static String MerchantUserMiddleName = "Middle"; + public static String EstateUserFamilyName = "Estate"; + public static String MerchantUserFamilyName = "Merchant"; public static CreateEstateUserRequest CreateEstateUserRequest = CreateEstateUserRequest.Create(TestData.EstateId, TestData.EstateUserEmailAddress, @@ -270,5 +279,13 @@ public static MerchantAggregate MerchantAggregateWithOperator() TestData.EstateUserFamilyName); public static Guid SecurityUserId = Guid.Parse("45B74A2E-BF92-44E9-A300-08E5CDEACFE3"); + + public static CreateMerchantUserRequest CreateMerchantUserRequest = CreateMerchantUserRequest.Create(TestData.EstateId, + TestData.MerchantId, + TestData.MerchantUserEmailAddress, + TestData.MerchantUserPassword, + TestData.MerchantUserGivenName, + TestData.MerchantUserMiddleName, + TestData.MerchantUserFamilyName); } } \ No newline at end of file diff --git a/EstateManagement.Tests/EstateManagement.Tests.csproj b/EstateManagement.Tests/EstateManagement.Tests.csproj index 95a791fd..b4bab1df 100644 --- a/EstateManagement.Tests/EstateManagement.Tests.csproj +++ b/EstateManagement.Tests/EstateManagement.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/EstateManagement/Controllers/MerchantController.cs b/EstateManagement/Controllers/MerchantController.cs index 7d46b63c..adb99d70 100644 --- a/EstateManagement/Controllers/MerchantController.cs +++ b/EstateManagement/Controllers/MerchantController.cs @@ -18,6 +18,8 @@ using CreateMerchantRequestDTO = DataTransferObjects.Requests.CreateMerchantRequest; using AssignOperatorToMerchantRequest = BusinessLogic.Requests.AssignOperatorToMerchantRequest; using AssignOperatorRequestDTO = DataTransferObjects.Requests.AssignOperatorRequest; + using CreateMerchantUserRequest = BusinessLogic.Requests.CreateMerchantUserRequest; + using CreateMerchantUserRequestDTO = DataTransferObjects.Requests.CreateMerchantUserRequest; /// /// @@ -167,6 +169,33 @@ public async Task AssignOperator([FromRoute] Guid estateId, }); } + [HttpPost] + [Route("{merchantId}/users")] + public async Task CreateMerchantUser([FromRoute] Guid estateId, + [FromRoute] Guid merchantId, + [FromBody] CreateMerchantUserRequestDTO createMerchantUserRequest, + CancellationToken cancellationToken) + { + // Create the command + CreateMerchantUserRequest request = CreateMerchantUserRequest.Create(estateId, merchantId, createMerchantUserRequest.EmailAddress, + createMerchantUserRequest.Password, + createMerchantUserRequest.GivenName, + createMerchantUserRequest.MiddleName, + createMerchantUserRequest.FamilyName); + + // Route the command + Guid userId = await this.Mediator.Send(request, cancellationToken); + + // return the result + return this.Created($"{MerchantController.ControllerRoute}/{merchantId}/users/{userId}", + new CreateMerchantUserResponse + { + EstateId = estateId, + MerchantId = merchantId, + UserId = userId + }); + } + #endregion #region Others diff --git a/EstateManagement/Startup.cs b/EstateManagement/Startup.cs index c1e9fa82..1241115d 100644 --- a/EstateManagement/Startup.cs +++ b/EstateManagement/Startup.cs @@ -158,6 +158,7 @@ public void ConfigureContainer(ContainerBuilder builder) builder.RegisterType().As>().SingleInstance(); builder.RegisterType().As>().SingleInstance(); builder.RegisterType().As>().SingleInstance(); + builder.RegisterType().As>().SingleInstance(); Func apiAddressResolver = (serviceName) => { return ConfigurationReader.GetBaseServerUri(serviceName).OriginalString; };