SpecTrace traceability matrix

NBK-BRD-2026-014 v1.0 → bank-app · generated 2026-09-27T04:18:05+00:00 · statuses verified against Surefire results
27
Requirements
11
Covered
11
Newly tested
3
Bugs found
2
Not implemented
39
Tests added
SpecTrace26.2 min
vs
Manual estimate~13 h 30 min
~13 h 3 min saved

30 min per requirement to locate code, find tests, write missing tests and update the matrix by hand

RequirementStatusImplementationTestsNotes
REQ-LN-03
Monthly instalment calculation
Bug found
LoanService#monthlyInstalment LoanService.java:60
NEW instalment_25000_at_5_5pct_36months FAIL
expected: 754.90 but was: 754.89
NEW instalment_60000_at_4_5pct_48months FAIL
expected: 1368.21 but was: 1368.20
Spec requires HALF_UP rounding; code uses RoundingMode.DOWN at LoanService.java:60 — 25,000/5.5%/36m yields 754.89 (expected 754.90) and 60,000/4.5%/48m yields 1368.20 (expected 1368.21).
REQ-SEC-05
Account ownership
Bug found
SecurityConfig#filterChain SecurityConfig.java:44
JwtAuthFilter#doFilterInternal JwtAuthFilter.java:33
AccountController#get AccountController.java:44
TransferController#transfer TransferController.java:35
NEW customerAttemptingTransferFromAnotherCustomersAccountIsRefusedWith403 FAIL
Status expected:<403> but was:<200>
NEW customerCanViewTheirOwnAccountDetails PASS
NEW customerRequestingAnotherCustomersAccountDetailsIsRefusedWith403 FAIL
Status expected:<403> but was:<200>
NEW requestWithoutValidSessionTokenIsRefused PASS
BUG 1: spec expects HTTP 403 when customer A reads customer B's account, but AccountController#get (line 44) returns 200 — no ownership check against the JWT principal. BUG 2: spec expects HTTP 403 when customer B initiates a transfer from customer A's account, but TransferController#transfer (line 35) returns 200 — no ownership check on fromAccount against the JWT principal.
REQ-TRF-03
Daily transfer limit
Bug found
TransferService#transfer TransferService.java:50
NEW dailyLimitExactly5000IsAccepted FAIL
Daily transfer limit exceeded for account SG38833806
NEW dailyLimitExceededIsRejected PASS
NEW previousDayTransfersDoNotCountTowardsLimit PASS
BUG: spec says exactly SGD 5,000.00 is allowed (inclusive), but line 50 uses `>= 0` (sentToday + amount >= DAILY_LIMIT) which rejects a transfer that brings the day's total to exactly 5,000.00; it should be `> 0`. (`TransferService.java:50`)
REQ-ACC-07
PIN lockout
Not implementednone foundno testsAccountService has no failed-PIN counter or lockout logic. LoginRateLimiter (com.spectrace.bank.security) is HTTP-level login throttling, not account-level PIN lockout, and does not satisfy this requirement.
REQ-TRF-07
Scheduled transfers
Not implementednone foundno testsNo scheduled transfer entity, scheduler, or cancellation logic exists anywhere in the transfers package or common package. The feature is entirely absent.
REQ-ACC-01
Account opening
Newly tested
AccountService#open AccountService.java:27
opensAccountWithOwnerAndInitialDeposit PASS
rejectsBlankOwnerOrNegativeDeposit PASS
NEW zeroInitialDepositIsAllowed PASS
Existing tests cover blank-name rejection, negative-deposit rejection, and initial balance/status. New test covers the missing criterion that SGD 0.00 initial deposit is allowed.
REQ-ACC-05
Account closure
Newly tested
AccountService#close AccountService.java:81
NEW closingAccountWithNonZeroBalanceIsRejected PASS
NEW closingAccountWithZeroBalanceSetsStatusToClosed PASS
close() guards on signum()!=0 and throws BankException, leaving status ACTIVE. With zero balance it sets status to CLOSED. Both acceptance criteria verified by new tests.
REQ-ACC-06
Frozen accounts
Newly tested
AccountService#requireActive AccountService.java:102
NEW depositToFrozenAccountIsRejected PASS
NEW frozenAccountBalanceUnchangedAfterRejectedOperations PASS
NEW withdrawalFromFrozenAccountIsRejected PASS
requireActive() throws BankException for any non-ACTIVE status; both deposit() and withdraw() delegate to it. All three acceptance criteria covered by new tests.
REQ-LN-04
Affordability check
Newly tested
LoanService#evaluate LoanService.java:79
NEW incomeOfExactly3xInstalmentIsEligible PASS
NEW insufficientIncomeIsRejectedWithAffordabilityReason PASS
evaluate() computes requiredIncome = instalment * 3 and rejects if monthlyIncome < requiredIncome; income equal to 3x is allowed. Note: affordability check uses the instalment computed by monthlyInstalment, which itself has the HALF_UP bug (REQ-LN-03), but the affordability logic itself is correct.
REQ-LN-06
Early repayment fee
Newly tested
LoanService#earlyRepaymentFee LoanService.java:63
NEW earlyRepaymentFee_10000_00 PASS
NEW earlyRepaymentFee_12345_67 PASS
earlyRepaymentFee() multiplies outstanding principal by 0.015 and rounds HALF_UP via Money.round(); both spec values (185.19 and 150.00) verified.
REQ-SEC-01
PIN protection
Newly tested
AccountService#open AccountService.java:38
Account#pinMatches Account.java:48
Account Account.java:15
NEW apiResponseContainsNoPinOrPinHashField PASS
NEW correctPinVerifiesAndIncorrectPinDoesNot PASS
NEW storedPinIsNotPlainText PASS
PIN is BCrypt-hashed in AccountService#open (line 38) and never returned by the API thanks to @JsonIgnore on Account#pinHash (line 15). All three acceptance criteria verified and passing.
REQ-SEC-02
Session tokens
Newly tested
JwtUtil#generate JwtUtil.java:33
JwtUtil#validate JwtUtil.java:47
JwtUtil#isValid JwtUtil.java:57
NEW expiredTokenIsRejected PASS
NEW freshlyIssuedTokenIsAcceptedAndIdentifiesAccount PASS
NEW tamperedTokenIsRejected PASS
NEW tokenLifetimeIs60Minutes PASS
JwtUtil signs HS256 tokens with subject=accountNumber and expiry=3600 s (60 min) as configured in application.properties. All four acceptance criteria verified and passing.
REQ-SEC-03
Login throttling
Newly tested
LoginRateLimiter#isBlocked LoginRateLimiter.java:33
LoginRateLimiter#recordFailure LoginRateLimiter.java:40
LoginRateLimiter#clearFailures LoginRateLimiter.java:46
AuthController#login AuthController.java:46
NEW after5FailedAttemptsNextAttemptIsRefused PASS
NEW failedAttemptsOnOneAccountDoNotAffectOtherAccounts PASS
NEW fourFailedAttemptsDoNotBlockAccount PASS
NEW successfulLoginResetsFailedAttemptCount PASS
Sliding-window rate limiter blocks after 5 failures in 900 s window; clearFailures() resets on success; per-account isolation verified. All four acceptance criteria passing.
REQ-SEC-04
Audit trail
Newly tested
AuditService#record AuditService.java:27
AuditService#getAll AuditService.java:32
AuthController#login AuthController.java:50
AccountService#deposit AccountService.java:59
AccountService#withdraw AccountService.java:69
AccountService#freeze AccountService.java:94
AccountService#close AccountService.java:87
NEW accountFreezesAndClosuresAreRecorded PASS
NEW auditEntriesCannotBeRemovedThroughInterface PASS
NEW depositsAndWithdrawalsAreRecorded PASS
NEW successfulAndFailedLoginsAreRecorded PASS
AuditService uses a CopyOnWriteArrayList and returns Collections.unmodifiableList; all required event types are recorded in the relevant service and controller methods. All four acceptance criteria passing.
REQ-TRF-04
New payee review
Newly tested
TransferService#transfer TransferService.java:56
NEW knownPayeeOver1000IsNotHeld PASS
NEW newPayeeExactly1000CompletesImmediately PASS
NEW newPayeeOver1000IsPendingReview PASS
All three acceptance criteria pass: >1000 to new payee → PENDING_REVIEW with no balance change; exactly 1000 → COMPLETED immediately; known payee (prior COMPLETED transfer) → not held regardless of amount.
REQ-TRF-06
Transfer history
Newly tested
TransferService#history TransferService.java:74
NEW historyIncludesBothIncomingAndOutgoing PASS
NEW historyIsNewestFirst PASS
All three acceptance criteria pass: incoming and outgoing transfers both appear; third-party transfers are excluded; results are ordered newest-first via reverse ledger iteration.
REQ-ACC-02
Account number format
Covered
AccountService#nextAccountNumber AccountService.java:110
accountNumbersFollowFormatAndAreUnique PASS
nextAccountNumber() generates 'SG' + 8-digit zero-padded random int and loops until unique. Test asserts both accounts match SG\d{8} and are distinct.
REQ-ACC-03
Deposits
Covered
AccountService#deposit AccountService.java:53
depositIncreasesBalanceAndMustBePositive PASS
deposit() rejects non-positive amounts and adds the exact amount (Money.round) to the balance. Test asserts 100 + 50.25 = 150.25 and zero is rejected.
REQ-ACC-04
Withdrawals and insufficient funds
Covered
AccountService#withdraw AccountService.java:63
withdrawalBeyondBalanceIsRejected PASS
withdraw() throws InsufficientFundsException when amount exceeds balance; balance is unchanged after the rejection. Test asserts both criteria.
REQ-ACC-08
Balance enquiry
Covered
AccountService#getBalance AccountService.java:77
balanceEnquiryIsRoundedToTwoDecimals PASS
getBalance() calls Money.round (HALF_UP, 2 d.p.). Existing test opens account with 10.005 and asserts getBalance() returns 10.01.
REQ-LN-01
Loan amount bounds
Covered
LoanService#evaluate LoanService.java:68
loanAmountMustBeWithinBounds PASS
evaluate() rejects amounts below SGD 1,000.00 and above SGD 100,000.00; boundary values 999.99 and 100,000.01 are rejected, 1,000.00 and 100,000.00 are accepted.
REQ-LN-02
Loan tenure
Covered
LoanService#evaluate LoanService.java:73
tenureMustBeBetween12And60Months PASS
evaluate() rejects tenure < 12 or > 60 months; boundary values 11 and 61 are rejected, 12 and 60 are accepted.
REQ-LN-05
Interest rate tiers
Covered
LoanService#interestRateFor LoanService.java:45
interestRateDependsOnAmountTier PASS
interestRateFor() returns 6.5% below SGD 20,000, 5.5% for SGD 20,000–50,000 inclusive, 4.5% above SGD 50,000; all tier boundaries tested.
REQ-LN-07
Loan decision
Covered
LoanService#apply LoanService.java:30
LoanService#get LoanService.java:37
everyApplicationIsApprovedOrRejectedWithReason PASS
Approved loans have null rejectionReason; rejected loans carry a non-blank reason. Applied loans are stored in a map and retrievable by id via get().
REQ-TRF-01
Atomic funds transfer
Covered
TransferService#transfer TransferService.java:37
failedCreditLeavesSourceUnchanged PASS
transferMovesFundsBetweenAccounts PASS
Both acceptance criteria covered by pre-existing tests: atomic debit+credit verified, and rollback on failed credit verified.
REQ-TRF-02
No self-transfers
Covered
TransferService#transfer TransferService.java:38
cannotTransferToSameAccount PASS
Self-transfer guard at line 38 throws IllegalArgumentException; existing test covers the criterion.
REQ-TRF-05
Transfer reference
Covered
TransferService#nextReference TransferService.java:104
everyTransferHasUniqueReferenceAndTimestamp PASS
Existing test verifies TRF-[A-Z0-9]{8} pattern, uniqueness across two transfers, and non-null timestamp.