Examples/Demos/Loan-Approval/Get-LoanDecision.Tests.ps1
|
BeforeAll { . "$PSScriptRoot/New-LoanApplication.ps1" . "$PSScriptRoot/Get-LoanDecision.ps1" Import-Module Jev -ErrorAction Stop } Describe 'Loan application construction' -Tag Local { It 'creates a reusable application with numeric fields' { $application = New-LoanApplication -Income 80000 -Requested 20000 -CreditScore 710 -Debt 5000 $application | Should -BeOfType [pscustomobject] $application.Income | Should -Be 80000 $application.Requested | Should -Be 20000 $application.CreditScore | Should -Be 710 $application.Debt | Should -Be 5000 $application.RequestedPercentIncome | Should -Be 25 $application.DebtPercentIncome | Should -Be 31.25 $application.CreditScore = 640 $application.CreditScore | Should -Be 640 } It 'preserves the calculated percentages for <Case>' -TestCases @( @{ Case = 'high debt'; Requested = 30000; Debt = 5000; RequestedPercent = 37.5; DebtPercent = 43.75 } @{ Case = 'amount just below half'; Requested = 39999; Debt = 0; RequestedPercent = 49.99875; DebtPercent = 49.99875 } @{ Case = 'debt exactly at the limit'; Requested = 20000; Debt = 12000; RequestedPercent = 25; DebtPercent = 40 } @{ Case = 'debt just above the limit'; Requested = 20000; Debt = 12001; RequestedPercent = 25; DebtPercent = 40.00125 } ) { param($Case, $Requested, $Debt, $RequestedPercent, $DebtPercent) $application = New-LoanApplication -Income 80000 -Requested $Requested -CreditScore 710 -Debt $Debt $application.RequestedPercentIncome | Should -Be ([decimal] $RequestedPercent) $application.DebtPercentIncome | Should -Be ([decimal] $DebtPercent) $application.RequestedPercentIncome | Should -BeOfType [decimal] $application.DebtPercentIncome | Should -BeOfType [decimal] } It 'refreshes percentages before sending a changed application to Jev' { Mock Invoke-Jev { @{ answers = @{ outcome = @{ choice = 'Approve' } } } } $application = New-LoanApplication -Income 80000 -Requested 20000 -CreditScore 710 -Debt 5000 $application.Income = 100000 $application | Get-LoanDecision | Out-Null Should -Invoke Invoke-Jev -Exactly 1 -Scope It -ParameterFilter { $State.Income -eq 100000 -and $State.RequestedPercentIncome -eq 20 -and $State.DebtPercentIncome -eq 25 } } It 'rejects an invalid edited field before calling Jev' { Mock Invoke-Jev { throw 'Invalid application reached Jev.' } $application = New-LoanApplication -Income 80000 -Requested 20000 -CreditScore 710 -Debt 5000 $application.Income = 0 { $application | Get-LoanDecision } | Should -Throw Should -Invoke Invoke-Jev -Exactly 0 -Scope It } It 'rejects an out-of-range <Field> value of <Value>' -TestCases @( @{ Field = 'Income'; Value = 0 } @{ Field = 'Income'; Value = 10000001 } @{ Field = 'Requested'; Value = 0 } @{ Field = 'Requested'; Value = 10000001 } @{ Field = 'CreditScore'; Value = 299 } @{ Field = 'CreditScore'; Value = 9000 } @{ Field = 'Debt'; Value = -1 } @{ Field = 'Debt'; Value = 10000001 } ) { param($Field, $Value) $parameters = @{ Income = 80000; Requested = 20000; CreditScore = 710; Debt = 5000 } $parameters[$Field] = $Value { New-LoanApplication @parameters } | Should -Throw } } Describe 'Loan decisions against the live Jev model' -Tag Live { It 'matches the ordered policy: <Case>' -TestCases @( @{ Case = 'approval'; Income = 80000; Requested = 20000; CreditScore = 710; Debt = 5000; Decision = 'Approve'; Reason = 'All approval conditions met' } @{ Case = 'amount rule before low credit and high DTI'; Income = 80000; Requested = 40000; CreditScore = 640; Debt = 30000; Decision = 'Deny'; Reason = 'Amount over half of income' } @{ Case = 'credit rule before high DTI'; Income = 80000; Requested = 20000; CreditScore = 640; Debt = 20000; Decision = 'Refer'; Reason = 'Credit score below 680' } @{ Case = 'high DTI'; Income = 80000; Requested = 30000; CreditScore = 710; Debt = 5000; Decision = 'Deny'; Reason = 'DTI over 0.4' } @{ Case = 'credit exactly 680'; Income = 80000; Requested = 20000; CreditScore = 680; Debt = 5000; Decision = 'Approve'; Reason = 'All approval conditions met' } @{ Case = 'credit just below 680'; Income = 80000; Requested = 20000; CreditScore = 679; Debt = 5000; Decision = 'Refer'; Reason = 'Credit score below 680' } @{ Case = 'amount exactly half'; Income = 80000; Requested = 40000; CreditScore = 710; Debt = 0; Decision = 'Deny'; Reason = 'Amount over half of income' } @{ Case = 'amount below half but high DTI'; Income = 80000; Requested = 39999; CreditScore = 710; Debt = 0; Decision = 'Deny'; Reason = 'DTI over 0.4' } @{ Case = 'DTI exactly 0.4'; Income = 80000; Requested = 20000; CreditScore = 680; Debt = 12000; Decision = 'Approve'; Reason = 'All approval conditions met' } @{ Case = 'DTI just over 0.4'; Income = 80000; Requested = 20000; CreditScore = 710; Debt = 12001; Decision = 'Deny'; Reason = 'DTI over 0.4' } ) { param($Case, $Income, $Requested, $CreditScore, $Debt, $Decision, $Reason) $state = New-LoanApplication -Income $Income -Requested $Requested -CreditScore $CreditScore -Debt $Debt $result = Get-LoanDecision -State $state $result.Decision | Should -Be $Decision $result.Reason | Should -Be $Reason $result | Should -BeOfType [pscustomobject] @($result.PSObject.Properties.Name) | Should -Be @('Decision', 'Reason') } It 'changes the decision when only the credit score changes' { $state = New-LoanApplication -Income 80000 -Requested 20000 -CreditScore 710 -Debt 5000 ($state | Get-LoanDecision).Decision | Should -Be 'Approve' $state.CreditScore = 640 ($state | Get-LoanDecision).Decision | Should -Be 'Refer' $state.Income | Should -Be 80000 $state.Requested | Should -Be 20000 $state.Debt | Should -Be 5000 } It 'returns the expected results for five piped states' { $states = @( New-LoanApplication -Income 80000 -Requested 20000 -CreditScore 710 -Debt 5000 New-LoanApplication -Income 80000 -Requested 40000 -CreditScore 640 -Debt 30000 New-LoanApplication -Income 80000 -Requested 20000 -CreditScore 640 -Debt 20000 New-LoanApplication -Income 80000 -Requested 30000 -CreditScore 710 -Debt 5000 New-LoanApplication -Income 80000 -Requested 20000 -CreditScore 680 -Debt 12000 ) $results = @($states | Get-LoanDecision) $results.Count | Should -Be 5 $results.Decision | Should -Be @('Approve', 'Deny', 'Refer', 'Deny', 'Approve') } It 'repeats the same baseline decision with the same model and state' { $state = New-LoanApplication -Income 80000 -Requested 20000 -CreditScore 710 -Debt 5000 $first = $state | Get-LoanDecision $second = $state | Get-LoanDecision $first.Decision | Should -Be 'Approve' $second.Decision | Should -Be $first.Decision $second.Reason | Should -Be $first.Reason } } |