Skip to content

Commit 1d8e38c

Browse files
Merge pull request #43 from StuartFerguson/task/#42_recordlocallydeclinedtxns
Locally declined transactions now recorded
2 parents 5facdf7 + e55eef4 commit 1d8e38c

17 files changed

Lines changed: 490 additions & 48 deletions

File tree

TransactionProcessor.BusinessLogic.Tests/Services/TransactionDomainServiceTests.cs

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public class TransactionDomainServiceTests
2727
[Fact]
2828
public async Task TransactionDomainService_ProcessLogonTransaction_TransactionIsProcessed()
2929
{
30-
var configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build();
30+
IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build();
3131
ConfigurationReader.Initialise(configurationRoot);
3232

3333
Logger.Initialise(NullLogger.Instance);
@@ -65,5 +65,134 @@ public async Task TransactionDomainService_ProcessLogonTransaction_TransactionIs
6565
response.ResponseCode.ShouldBe(TestData.ResponseCode);
6666
response.ResponseMessage.ShouldBe(TestData.ResponseMessage);
6767
}
68+
69+
[Fact]
70+
public async Task TransactionDomainService_ProcessLogonTransaction_MerchantWithNullDevices_TransactionIsProcessed()
71+
{
72+
IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build();
73+
ConfigurationReader.Initialise(configurationRoot);
74+
75+
Logger.Initialise(NullLogger.Instance);
76+
77+
Mock<IAggregateRepositoryManager> aggregateRepositoryManager = new Mock<IAggregateRepositoryManager>();
78+
Mock<IAggregateRepository<TransactionAggregate>> transactionAggregateRepository = new Mock<IAggregateRepository<TransactionAggregate>>();
79+
80+
aggregateRepositoryManager.Setup(x => x.GetAggregateRepository<TransactionAggregate>(It.IsAny<Guid>())).Returns(transactionAggregateRepository.Object);
81+
transactionAggregateRepository.SetupSequence(t => t.GetLatestVersion(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
82+
.ReturnsAsync(TestData.GetEmptyTransactionAggregate)
83+
.ReturnsAsync(TestData.GetStartedTransactionAggregate)
84+
.ReturnsAsync(TestData.GetLocallyAuthorisedTransactionAggregate)
85+
.ReturnsAsync(TestData.GetCompletedTransactionAggregate);
86+
transactionAggregateRepository.Setup(t => t.SaveChanges(It.IsAny<TransactionAggregate>(), It.IsAny<CancellationToken>())).Returns(Task.CompletedTask);
87+
88+
Mock<IEstateClient> estateClient = new Mock<IEstateClient>();
89+
Mock<ISecurityServiceClient> securityServiceClient = new Mock<ISecurityServiceClient>();
90+
91+
securityServiceClient.Setup(s => s.GetToken(It.IsAny<String>(), It.IsAny<String>(), It.IsAny<CancellationToken>())).ReturnsAsync(TestData.TokenResponse);
92+
estateClient.Setup(e => e.GetMerchant(It.IsAny<String>(), It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
93+
.ReturnsAsync(TestData.GetMerchantResponseWithNullDevices);
94+
95+
TransactionDomainService transactionDomainService =
96+
new TransactionDomainService(aggregateRepositoryManager.Object, estateClient.Object, securityServiceClient.Object);
97+
98+
ProcessLogonTransactionResponse response = await transactionDomainService.ProcessLogonTransaction(TestData.TransactionId,
99+
TestData.EstateId,
100+
TestData.MerchantId,
101+
TestData.TransactionDateTime,
102+
TestData.TransactionNumber,
103+
TestData.DeviceIdentifier,
104+
CancellationToken.None);
105+
106+
response.ShouldNotBeNull();
107+
response.ResponseCode.ShouldBe(TestData.ResponseCode);
108+
response.ResponseMessage.ShouldBe(TestData.ResponseMessage);
109+
}
110+
111+
[Fact]
112+
public async Task TransactionDomainService_ProcessLogonTransaction_MerchantWithNoDevices_TransactionIsProcessed()
113+
{
114+
IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build();
115+
ConfigurationReader.Initialise(configurationRoot);
116+
117+
Logger.Initialise(NullLogger.Instance);
118+
119+
Mock<IAggregateRepositoryManager> aggregateRepositoryManager = new Mock<IAggregateRepositoryManager>();
120+
Mock<IAggregateRepository<TransactionAggregate>> transactionAggregateRepository = new Mock<IAggregateRepository<TransactionAggregate>>();
121+
122+
aggregateRepositoryManager.Setup(x => x.GetAggregateRepository<TransactionAggregate>(It.IsAny<Guid>())).Returns(transactionAggregateRepository.Object);
123+
transactionAggregateRepository.SetupSequence(t => t.GetLatestVersion(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
124+
.ReturnsAsync(TestData.GetEmptyTransactionAggregate)
125+
.ReturnsAsync(TestData.GetStartedTransactionAggregate)
126+
.ReturnsAsync(TestData.GetLocallyAuthorisedTransactionAggregate)
127+
.ReturnsAsync(TestData.GetCompletedTransactionAggregate);
128+
transactionAggregateRepository.Setup(t => t.SaveChanges(It.IsAny<TransactionAggregate>(), It.IsAny<CancellationToken>())).Returns(Task.CompletedTask);
129+
130+
Mock<IEstateClient> estateClient = new Mock<IEstateClient>();
131+
Mock<ISecurityServiceClient> securityServiceClient = new Mock<ISecurityServiceClient>();
132+
133+
securityServiceClient.Setup(s => s.GetToken(It.IsAny<String>(), It.IsAny<String>(), It.IsAny<CancellationToken>())).ReturnsAsync(TestData.TokenResponse);
134+
estateClient.Setup(e => e.GetMerchant(It.IsAny<String>(), It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
135+
.ReturnsAsync(TestData.GetMerchantResponseWithNoDevices);
136+
137+
TransactionDomainService transactionDomainService =
138+
new TransactionDomainService(aggregateRepositoryManager.Object, estateClient.Object, securityServiceClient.Object);
139+
140+
ProcessLogonTransactionResponse response = await transactionDomainService.ProcessLogonTransaction(TestData.TransactionId,
141+
TestData.EstateId,
142+
TestData.MerchantId,
143+
TestData.TransactionDateTime,
144+
TestData.TransactionNumber,
145+
TestData.DeviceIdentifier,
146+
CancellationToken.None);
147+
148+
response.ShouldNotBeNull();
149+
response.ResponseCode.ShouldBe(TestData.ResponseCode);
150+
response.ResponseMessage.ShouldBe(TestData.ResponseMessage);
151+
}
152+
153+
[Fact]
154+
public async Task TransactionDomainService_ProcessLogonTransaction_IncorrectDevice_TransactionIsProcessed()
155+
{
156+
IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build();
157+
ConfigurationReader.Initialise(configurationRoot);
158+
159+
Logger.Initialise(NullLogger.Instance);
160+
161+
Mock<IAggregateRepositoryManager> aggregateRepositoryManager = new Mock<IAggregateRepositoryManager>();
162+
Mock<IAggregateRepository<TransactionAggregate>> transactionAggregateRepository = new Mock<IAggregateRepository<TransactionAggregate>>();
163+
164+
aggregateRepositoryManager.Setup(x => x.GetAggregateRepository<TransactionAggregate>(It.IsAny<Guid>())).Returns(transactionAggregateRepository.Object);
165+
transactionAggregateRepository.SetupSequence(t => t.GetLatestVersion(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
166+
.ReturnsAsync(TestData.GetEmptyTransactionAggregate)
167+
.ReturnsAsync(TestData.GetStartedTransactionAggregate)
168+
.ReturnsAsync(TestData.GetLocallyAuthorisedTransactionAggregate)
169+
.ReturnsAsync(TestData.GetCompletedTransactionAggregate);
170+
transactionAggregateRepository.Setup(t => t.SaveChanges(It.IsAny<TransactionAggregate>(), It.IsAny<CancellationToken>())).Returns(Task.CompletedTask);
171+
172+
Mock<IEstateClient> estateClient = new Mock<IEstateClient>();
173+
Mock<ISecurityServiceClient> securityServiceClient = new Mock<ISecurityServiceClient>();
174+
175+
securityServiceClient.Setup(s => s.GetToken(It.IsAny<String>(), It.IsAny<String>(), It.IsAny<CancellationToken>())).ReturnsAsync(TestData.TokenResponse);
176+
estateClient.Setup(e => e.GetMerchant(It.IsAny<String>(), It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
177+
.ReturnsAsync(TestData.GetMerchantResponse);
178+
179+
TransactionDomainService transactionDomainService =
180+
new TransactionDomainService(aggregateRepositoryManager.Object, estateClient.Object, securityServiceClient.Object);
181+
182+
ProcessLogonTransactionResponse response = await transactionDomainService.ProcessLogonTransaction(TestData.TransactionId,
183+
TestData.EstateId,
184+
TestData.MerchantId,
185+
TestData.TransactionDateTime,
186+
TestData.TransactionNumber,
187+
TestData.DeviceIdentifier1,
188+
CancellationToken.None);
189+
190+
response.ShouldNotBeNull();
191+
response.ResponseCode.ShouldBe(TestData.ResponseCode);
192+
response.ResponseMessage.ShouldBe(TestData.ResponseMessage);
193+
}
194+
195+
// Txn for a Different Device (Causes a Validation Exception)
196+
68197
}
69198
}

TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,9 @@ public async Task<ProcessLogonTransactionResponse> ProcessLogonTransaction(Guid
102102
else
103103
{
104104
// Record the failure
105-
throw new NotImplementedException();
105+
transactionAggregate = await transactionAggregateRepository.GetLatestVersion(transactionId, cancellationToken);
106+
transactionAggregate.DeclineTransactionLocally(((Int32)validationResult.responseCode).ToString().PadLeft(4, '0'), validationResult.responseMessage);
107+
await transactionAggregateRepository.SaveChanges(transactionAggregate, cancellationToken);
106108
}
107109

108110
transactionAggregate = await transactionAggregateRepository.GetLatestVersion(transactionId, cancellationToken);
@@ -191,30 +193,4 @@ public enum TransactionResponseCode
191193
Success = 0,
192194
InvalidDeviceIdentifier = 1000
193195
}
194-
195-
public class TransactionValidationException : Exception
196-
{
197-
public TransactionResponseCode ResponseCode { get; private set; }
198-
199-
/// <summary>
200-
/// Initializes a new instance of the <see cref="TransactionValidationException" /> class.
201-
/// </summary>
202-
/// <param name="message">The message that describes the error.</param>
203-
/// <param name="responseCode">The response code.</param>
204-
public TransactionValidationException(String message, TransactionResponseCode responseCode) : this(message, responseCode, null)
205-
{
206-
207-
}
208-
209-
/// <summary>
210-
/// Initializes a new instance of the <see cref="TransactionValidationException" /> class.
211-
/// </summary>
212-
/// <param name="message">The error message that explains the reason for the exception.</param>
213-
/// <param name="responseCode">The response code.</param>
214-
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified.</param>
215-
public TransactionValidationException(String message, TransactionResponseCode responseCode, Exception innerException) : base(message, innerException)
216-
{
217-
this.ResponseCode = responseCode;
218-
}
219-
}
220196
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
namespace TransactionProcessor.BusinessLogic.Services
2+
{
3+
using System;
4+
using System.Diagnostics.CodeAnalysis;
5+
6+
[ExcludeFromCodeCoverage]
7+
public class TransactionValidationException : Exception
8+
{
9+
public TransactionResponseCode ResponseCode { get; private set; }
10+
11+
/// <summary>
12+
/// Initializes a new instance of the <see cref="TransactionValidationException" /> class.
13+
/// </summary>
14+
/// <param name="message">The message that describes the error.</param>
15+
/// <param name="responseCode">The response code.</param>
16+
public TransactionValidationException(String message, TransactionResponseCode responseCode) : this(message, responseCode, null)
17+
{
18+
19+
}
20+
21+
/// <summary>
22+
/// Initializes a new instance of the <see cref="TransactionValidationException" /> class.
23+
/// </summary>
24+
/// <param name="message">The error message that explains the reason for the exception.</param>
25+
/// <param name="responseCode">The response code.</param>
26+
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified.</param>
27+
public TransactionValidationException(String message, TransactionResponseCode responseCode, Exception innerException) : base(message, innerException)
28+
{
29+
this.ResponseCode = responseCode;
30+
}
31+
}
32+
}

TransactionProcessor.DataTransferObjects/DataTransferObject.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
namespace TransactionProcessor.DataTransferObjects
22
{
33
using System;
4+
using System.Diagnostics.CodeAnalysis;
45

56
/// <summary>
67
///
78
/// </summary>
9+
[ExcludeFromCodeCoverage]
810
public abstract class DataTransferObject
911
{
1012
#region Properties
@@ -28,10 +30,4 @@ public abstract class DataTransferObject
2830

2931
#endregion
3032
}
31-
32-
public class MetadataContants
33-
{
34-
public const String KeyNameEstateId = "EstateId";
35-
public const String KeyNameMerchantId = "MerchantId";
36-
}
3733
}

TransactionProcessor.DataTransferObjects/LogonTransactionResponse.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,20 @@ public class LogonTransactionResponse
1111
{
1212
#region Properties
1313

14+
/// <summary>
15+
/// Gets or sets the estate identifier.
16+
/// </summary>
17+
/// <value>
18+
/// The estate identifier.
19+
/// </value>
1420
public Guid EstateId { get; set; }
21+
22+
/// <summary>
23+
/// Gets or sets the merchant identifier.
24+
/// </summary>
25+
/// <value>
26+
/// The merchant identifier.
27+
/// </value>
1528
public Guid MerchantId { get; set; }
1629

1730
/// <summary>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
namespace TransactionProcessor.DataTransferObjects
2+
{
3+
using System;
4+
using System.Diagnostics.CodeAnalysis;
5+
6+
/// <summary>
7+
///
8+
/// </summary>
9+
[ExcludeFromCodeCoverage]
10+
public class MetadataContants
11+
{
12+
#region Others
13+
14+
/// <summary>
15+
/// The key name estate identifier
16+
/// </summary>
17+
public const String KeyNameEstateId = "EstateId";
18+
19+
/// <summary>
20+
/// The key name merchant identifier
21+
/// </summary>
22+
public const String KeyNameMerchantId = "MerchantId";
23+
24+
#endregion
25+
}
26+
}

TransactionProcessor.DataTransferObjects/SerialisedMessage.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
{
33
using System;
44
using System.Collections.Generic;
5+
using System.Diagnostics.CodeAnalysis;
56
using Newtonsoft.Json;
67

78
/// <summary>
89
///
910
/// </summary>
11+
[ExcludeFromCodeCoverage]
1012
public class SerialisedMessage
1113
{
1214
#region Constructors

TransactionProcessor.IntegrationTests/LogonTransaction/LogonTransaction.feature

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ Scenario: Logon Transaction with Existing Device
6464
| EstateName | MerchantName | TransactionNumber | ResponseCode | ResponseMessage |
6565
| Test Estate 1 | Test Merchant 1 | 1 | 0000 | SUCCESS |
6666

67-
@ignore
67+
@PRTest
6868
Scenario: Logon Transaction with Invalid Device
6969

7070
Given I have assigned the following devices to the merchants
@@ -76,5 +76,5 @@ Scenario: Logon Transaction with Invalid Device
7676
| Today | 1 | Logon | Test Merchant 1 | 123456781 | Test Estate 1 |
7777

7878
Then transaction response should contain the following information
79-
| EstateName | MerchantName | TransactionNumber | ResponseCode | ResponseMessage |
80-
| Test Estate 1 | Test Merchant 1 | 1 | 0000 | SUCCESS |
79+
| EstateName | MerchantName | TransactionNumber | ResponseCode | ResponseMessage |
80+
| Test Estate 1 | Test Merchant 1 | 1 | 1000 | Device Identifier 123456781 not valid for Merchant Test Merchant 1 |

TransactionProcessor.IntegrationTests/LogonTransaction/LogonTransaction.feature.cs

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)