Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<Nullable>disable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,15 @@ public void LoanApplicationDecisionService_GreenApplication_CanBeAccepted()
GivenOperator().WithLogin("admin").Build()
});

var existingApplications = new InMemoryLoanApplicationRepository(new []
{
var existingApplications = new InMemoryLoanApplicationRepository([
GivenLoanApplication()
.WithNumber("123")
.WithCustomer(customer => customer.WithAge(25).WithIncome(15_000M))
.WithLoan(loan => loan.WithAmount(200_000).WithNumberOfYears(25).WithInterestRate(1.1M))
.WithProperty(prop => prop.WithValue(250_000M))
.Evaluated()
.Build()
});
]);

var eventBus = new InMemoryBus();

Expand Down Expand Up @@ -62,16 +61,15 @@ public void LoanApplicationDecisionService_GreenApplication_CanBeRejected()
GivenOperator().WithLogin("admin").Build()
});

var existingApplications = new InMemoryLoanApplicationRepository(new []
{
var existingApplications = new InMemoryLoanApplicationRepository([
GivenLoanApplication()
.WithNumber("123")
.WithCustomer(customer => customer.WithAge(25).WithIncome(15_000M))
.WithLoan(loan => loan.WithAmount(200_000).WithNumberOfYears(25).WithInterestRate(1.1M))
.WithProperty(prop => prop.WithValue(250_000M))
.Evaluated()
.Build()
});
]);

var eventBus = new InMemoryBus();

Expand All @@ -98,9 +96,8 @@ public void LoanApplicationDecisionService_GreenApplication_CanBeRejected()

private ClaimsPrincipal OperatorIdentity(string login)
{
return new ClaimsPrincipal(new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, login)
}));
return new ClaimsPrincipal(new ClaimsIdentity([
new Claim(ClaimTypes.Name, login)
]));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,14 @@ public class LoanApplicationEvaluationServiceTests
[Fact]
public void LoanApplicationEvaluationService_ApplicationThatSatisfiesAllRules_IsEvaluatedGreen()
{
var existingApplications = new InMemoryLoanApplicationRepository(new []
{
var existingApplications = new InMemoryLoanApplicationRepository([
GivenLoanApplication()
.WithNumber("123")
.WithCustomer(customer => customer.WithAge(25).WithIncome(15_000M))
.WithLoan(loan => loan.WithAmount(200_000).WithNumberOfYears(25).WithInterestRate(1.1M))
.WithProperty(prop => prop.WithValue(250_000M))
.Build()
});
]);

var evaluationService = new LoanApplicationEvaluationService
(
Expand All @@ -39,15 +38,14 @@ public void LoanApplicationEvaluationService_ApplicationThatSatisfiesAllRules_Is
[Fact]
public void LoanApplicationEvaluationService_ApplicationThatDoesNotSatisfyAllRules_IsEvaluatedRedAndRejected()
{
var existingApplications = new InMemoryLoanApplicationRepository(new []
{
var existingApplications = new InMemoryLoanApplicationRepository([
GivenLoanApplication()
.WithNumber("123")
.WithCustomer(customer => customer.WithAge(55).WithIncome(15_000M))
.WithLoan(loan => loan.WithAmount(200_000).WithNumberOfYears(25).WithInterestRate(1.1M))
.WithProperty(prop => prop.WithValue(250_000M))
.Build()
});
]);

var evaluationService = new LoanApplicationEvaluationService
(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public void LoanApplicationSubmissionService_ValidApplication_GetsSubmitted()
CustomerNationalIdentifier : "11111111119",
CustomerFirstName : "Frank",
CustomerLastName : "Oz",
CustomerBirthdate : SysTime.Now().AddYears(-25),
CustomerBirthdate : SysTime.Today().AddYears(-25),
CustomerMonthlyIncome : 10_000M,
CustomerAddress : new AddressDto
(
Expand Down Expand Up @@ -87,7 +87,7 @@ public void LoanApplicationSubmissionService_InvalidApplication_IsNotSaved()
CustomerNationalIdentifier : "11111111119111",
CustomerFirstName : "Frank",
CustomerLastName : "Oz",
CustomerBirthdate : SysTime.Now().AddYears(-25),
CustomerBirthdate : SysTime.Today().AddYears(-25),
CustomerMonthlyIncome : 10_000M,
CustomerAddress : new AddressDto
(
Expand Down Expand Up @@ -120,9 +120,8 @@ public void LoanApplicationSubmissionService_InvalidApplication_IsNotSaved()

private ClaimsPrincipal OperatorIdentity(string login)
{
return new ClaimsPrincipal(new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, login)
}));
return new ClaimsPrincipal(new ClaimsIdentity([
new Claim(ClaimTypes.Name, login)
]));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using FluentAssertions;
using FluentAssertions.Execution;
using FluentAssertions.Primitives;
using LoanApplication.TacticalDdd.DomainModel.Ddd;

namespace LoanApplication.TacticalDdd.Tests.Asserts;
Expand All @@ -13,26 +12,21 @@ public static DomainEventsAssert Should(this IEnumerable<DomainEvent> events)
}
}

public class DomainEventsAssert : ReferenceTypeAssertions<IEnumerable<DomainEvent>,DomainEventsAssert>
public class DomainEventsAssert(IEnumerable<DomainEvent> events)
{
public DomainEventsAssert(IEnumerable<DomainEvent> events) : base(events)
{
}

public AndConstraint<DomainEventsAssert> HaveExpectedNumberOfEvents(int expectedNumberOfEvents)
{
Subject.Count().Should().Be(expectedNumberOfEvents);
events.Count().Should().Be(expectedNumberOfEvents);
return new AndConstraint<DomainEventsAssert>(this);
}

public AndConstraint<DomainEventsAssert> ContainEvent<T>(Predicate<T> matcher) where T : DomainEvent
{
Execute.Assertion
.ForCondition(Subject.Any(e => e.GetType() == typeof(T) && matcher((T) e)))
.FailWith("List of events does not contain any that meets criteria");

using (new AssertionScope())
{
events.Any(e => e.GetType() == typeof(T) && matcher((T)e))
.Should().BeTrue("List of events does not contain any that meets criteria");
}
return new AndConstraint<DomainEventsAssert>(this);
}

protected override string Identifier => "DomainEventsAssert";
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,11 @@ public static LoanApplicationAssert Should(this DomainModel.LoanApplication loan
=> new LoanApplicationAssert(loanApplication);
}

public class LoanApplicationAssert : ReferenceTypeAssertions<DomainModel.LoanApplication,LoanApplicationAssert>
public class LoanApplicationAssert(DomainModel.LoanApplication loanApplication)
{
public LoanApplicationAssert(DomainModel.LoanApplication loanApplication)
: base(loanApplication)
{

}

public AndConstraint<LoanApplicationAssert> BeInStatus(LoanApplicationStatus expectedStatus)
{
Subject.Status.Should().Be(expectedStatus);
loanApplication.Status.Should().Be(expectedStatus);
return new AndConstraint<LoanApplicationAssert>(this);
}

Expand All @@ -41,13 +35,13 @@ public AndConstraint<LoanApplicationAssert> BeNew()

public AndConstraint<LoanApplicationAssert> ScoreIsNull()
{
Subject.Score.Should().BeNull();
loanApplication.Score.Should().BeNull();
return new AndConstraint<LoanApplicationAssert>(this);
}

public AndConstraint<LoanApplicationAssert> ScoreIs(ApplicationScore expectedScore)
{
Subject.Score?.Score.Should().Be(expectedScore);
loanApplication.Score?.Score.Should().Be(expectedScore);
return new AndConstraint<LoanApplicationAssert>(this);
}

Expand All @@ -61,5 +55,4 @@ public AndConstraint<LoanApplicationAssert> HaveGreenScore()
return ScoreIs(ApplicationScore.Green);
}

protected override string Identifier => "LoanApplicationAssert";
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class LoanApplicationBuilder
private LoanApplicationNumber applicationNumber = new LoanApplicationNumber(Guid.NewGuid().ToString());
private bool evaluated = false;
private LoanApplicationStatus targetStatus = LoanApplicationStatus.New;
private ScoringRulesFactory scoringRulesFactory = new ScoringRulesFactory(new DebtorRegistryMock());
private readonly ScoringRulesFactory scoringRulesFactory = new ScoringRulesFactory(new DebtorRegistryMock());
public static LoanApplicationBuilder GivenLoanApplication() => new LoanApplicationBuilder();

public LoanApplicationBuilder Accepted()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<Nullable>disable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PackageReference Include="FluentAssertions" Version="8.8.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.2" />
<PackageReference Include="coverlet.collector" Version="6.0.4" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ public record LoanApplicationDto
string CustomerNationalIdentifier,
string CustomerFirstName,
string CustomerLastName,
DateTime CustomerBirthdate,
DateOnly CustomerBirthdate,
decimal CustomerMonthlyIncome,
AddressDto CustomerAddress,
decimal PropertyValue,
AddressDto PropertyAddress,
decimal LoanAmount,
int LoanNumberOfYears,
decimal InterestRate,
DateTime? DecisionDate,
DateOnly? DecisionDate,
string DecisionBy,
string RegisteredBy,
DateTime RegistrationDate
DateOnly RegistrationDate
)
{
//this one is needed to allow dapper to create instance of it using reflection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ public record LoanApplicationInfoDto
string Number,
string Status,
string CustomerName,
DateTime? DecisionDate,
DateOnly? DecisionDate,
decimal LoanAmount,
string DecisionBy
);
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public record LoanApplicationSubmissionDto
string CustomerNationalIdentifier,
string CustomerFirstName,
string CustomerLastName,
DateTime CustomerBirthdate,
DateOnly CustomerBirthdate,
decimal CustomerMonthlyIncome,
AddressDto CustomerAddress,
decimal PropertyValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,13 @@ namespace LoanApplication.TacticalDdd.Application;
using DomainModel;
using DomainModel.Ddd;

public class LoanApplicationEvaluationService
public class LoanApplicationEvaluationService(
IUnitOfWork unitOfWork,
ILoanApplicationRepository loanApplications,
IDebtorRegistry debtorRegistry)
{
private readonly IUnitOfWork unitOfWork;
private readonly ILoanApplicationRepository loanApplications;
private readonly ScoringRulesFactory scoringRulesFactory;

public LoanApplicationEvaluationService
(
IUnitOfWork unitOfWork,
ILoanApplicationRepository loanApplications,
IDebtorRegistry debtorRegistry
)
{
this.unitOfWork = unitOfWork;
this.loanApplications = loanApplications;
this.scoringRulesFactory = new ScoringRulesFactory(debtorRegistry);
}
private readonly ScoringRulesFactory scoringRulesFactory = new(debtorRegistry);

public void EvaluateLoanApplication(string applicationNumber)
{
var loanApplication = loanApplications.WithNumber(LoanApplicationNumber.Of(applicationNumber));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public string SubmitLoanApplication(LoanApplicationSubmissionDto loanApplication
(
new NationalIdentifier(loanApplicationDto.CustomerNationalIdentifier),
new Name(loanApplicationDto.CustomerFirstName, loanApplicationDto.CustomerLastName),
DateOnly.FromDateTime(loanApplicationDto.CustomerBirthdate),
loanApplicationDto.CustomerBirthdate,
new MonetaryAmount(loanApplicationDto.CustomerMonthlyIncome),
new Address
(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using LoanApplication.TacticalDdd.DomainModel.Ddd;
using Newtonsoft.Json;

namespace LoanApplication.TacticalDdd.DomainModel;

Expand All @@ -14,8 +13,7 @@ public Registration(DateOnly registrationDate, Operator registeredBy)
{
}

[JsonConstructor]
public Registration(DateOnly registrationDate, OperatorId registeredBy)
private Registration(DateOnly registrationDate, OperatorId registeredBy)
{
RegistrationDate = registrationDate;
RegisteredBy = registeredBy;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
using LoanApplication.TacticalDdd.DomainModel.Ddd;
using Newtonsoft.Json;

namespace LoanApplication.TacticalDdd.DomainModel;

public class ScoringResult : ValueObject<ScoringResult>
{
public ApplicationScore? Score { get; }
public ApplicationScore Score { get; }
public string Explanation { get; }

[JsonConstructor]
private ScoringResult(ApplicationScore? score, string explanation)
private ScoringResult(ApplicationScore score, string explanation)
{
Score = score;
Explanation = explanation;
Expand All @@ -34,5 +32,5 @@ public static ScoringResult Red(string[] messages)
=> new (ApplicationScore.Red, string.Join(Environment.NewLine,messages));


public bool IsRed() =>Score == ApplicationScore.Red;
public bool IsRed() => Score == ApplicationScore.Red;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public ScoringResult Evaluate(LoanApplication loanApplication)
.Where(r => !r.IsSatisfiedBy(loanApplication))
.ToList();

return brokenRules.Any() ?
return brokenRules.Count != 0 ?
ScoringResult.Red(brokenRules.Select(r=>r.Message).ToArray()) : ScoringResult.Green();
}
}
Expand Down Expand Up @@ -52,15 +52,8 @@ public bool IsSatisfiedBy(LoanApplication loanApplication)
public string Message => "Installment is higher than 15% of customer's income.";
}

public class CustomerIsNotARegisteredDebtor : IScoringRule
public class CustomerIsNotARegisteredDebtor(IDebtorRegistry debtorRegistry) : IScoringRule
{
private readonly IDebtorRegistry debtorRegistry;

public CustomerIsNotARegisteredDebtor(IDebtorRegistry debtorRegistry)
{
this.debtorRegistry = debtorRegistry;
}

public bool IsSatisfiedBy(LoanApplication loanApplication)
{
return !debtorRegistry.IsRegisteredDebtor(loanApplication.Customer);
Expand Down
Loading
Loading