Files
alrahma_sunday_school/tests/WithdrawalRefundCalculatorTest.php
root d906a915d6
Deploy to Shared Hosting / Shared hosting deploy (push) Failing after 47s
Tests / PHPUnit (push) Failing after 1m20s
add refund logic and fix books inventory logic
2026-08-22 13:44:25 -04:00

252 lines
9.5 KiB
PHP

<?php
namespace Tests\Unit;
use App\Services\WithdrawalRefundCalculator;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
final class WithdrawalRefundCalculatorTest extends TestCase
{
private WithdrawalRefundCalculator $calculator;
protected function setUp(): void
{
$this->calculator = new WithdrawalRefundCalculator();
}
public function testOneWeekRetainsIssuedBooksAndOneInstructionalWeek(): void
{
$result = $this->calculator->calculate($this->baseInput([
'valid_payment_cents' => 38000,
]));
self::assertSame(210, $result['total_chargeable_days']);
self::assertSame(7, $result['studied_calendar_days']);
self::assertSame(1, $result['studied_weeks']);
self::assertSame(5000, $result['issued_book_charge_cents']);
self::assertSame(33000, $result['annual_instruction_cents']);
self::assertSame(1100, $result['earned_tuition_cents']);
self::assertSame(6100, $result['retained_charge_cents']);
self::assertSame(31900, $result['new_refund_request_cents']);
self::assertSame(0, $result['balance_due_cents']);
}
public function testPartialPaymentRefundsOnlyTheAmountAboveRetainedCharge(): void
{
$result = $this->calculator->calculate($this->baseInput([
'valid_payment_cents' => 10000,
]));
self::assertSame(3900, $result['new_refund_request_cents']);
self::assertSame(0, $result['balance_due_cents']);
}
public function testPartialPaymentBelowRetainedChargeProducesBalanceAndNoRefund(): void
{
$result = $this->calculator->calculate($this->baseInput([
'valid_payment_cents' => 5000,
]));
self::assertSame(0, $result['new_refund_request_cents']);
self::assertSame(1100, $result['balance_due_cents']);
}
public function testStartedSecondSevenDayPeriodChargesTwoWeeks(): void
{
$result = $this->calculator->calculate($this->baseInput([
'withdrawal_request_date' => '2026-08-08',
]));
self::assertSame(8, $result['studied_calendar_days']);
self::assertSame(2, $result['studied_weeks']);
self::assertSame(2200, $result['earned_tuition_cents']);
self::assertSame(7200, $result['retained_charge_cents']);
}
public function testCompletedPayoutAndReservationCannotBeRefundedAgain(): void
{
$result = $this->calculator->calculate($this->baseInput([
'valid_payment_cents' => 38000,
'completed_payout_cents' => 10000,
'open_reservation_cents' => 5000,
]));
self::assertSame(21900, $result['refundable_credit_cents']);
self::assertSame(16900, $result['new_refund_request_cents']);
}
public function testIssuedBooksCannotExceedInclusiveAnnualAllocation(): void
{
$this->expectException(InvalidArgumentException::class);
$this->calculator->calculate($this->baseInput([
'issued_book_charge_cents' => 38001,
]));
}
public function testBooksExcludedModeIsRejected(): void
{
$this->expectException(InvalidArgumentException::class);
$this->calculator->calculate($this->baseInput([
'annual_fee_includes_books' => false,
]));
}
public function testDayOneChargesOneStartedWeek(): void
{
$result = $this->calculator->calculate($this->baseInput([
'withdrawal_request_date' => '2026-08-01',
]));
self::assertSame(1, $result['studied_calendar_days']);
self::assertSame(1, $result['studied_weeks']);
}
public function testWithdrawalBeforeChargeStartChargesNoTuitionButRetainsBooks(): void
{
$result = $this->calculator->calculate($this->baseInput([
'enrollment_date' => '2026-08-10',
'withdrawal_request_date' => '2026-08-09',
]));
self::assertSame(0, $result['studied_calendar_days']);
self::assertSame(0, $result['studied_weeks']);
self::assertSame(5000, $result['retained_charge_cents']);
}
public function testLateEnrollmentBecomesChargeStart(): void
{
$result = $this->calculator->calculate($this->baseInput([
'enrollment_date' => '2026-08-20',
'withdrawal_request_date' => '2026-08-26',
]));
self::assertSame('2026-08-20', $result['charge_start_date']);
self::assertSame(7, $result['studied_calendar_days']);
self::assertSame(1, $result['studied_weeks']);
}
public function testEnrollmentBeforeSchoolStartsUsesSchoolStart(): void
{
$result = $this->calculator->calculate($this->baseInput([
'enrollment_date' => '2026-07-01',
'withdrawal_request_date' => '2026-08-07',
]));
self::assertSame('2026-08-01', $result['charge_start_date']);
self::assertSame(1, $result['studied_weeks']);
}
public function testDurationCapsAtTotalInstructionalWeeks(): void
{
$result = $this->calculator->calculate($this->baseInput([
'withdrawal_request_date' => '2027-12-31',
'valid_payment_cents' => 38000,
]));
self::assertSame(210, $result['studied_calendar_days']);
self::assertSame(30, $result['studied_weeks']);
self::assertSame(38000, $result['retained_charge_cents']);
self::assertSame(0, $result['new_refund_request_cents']);
}
public function testOtherChargesRemainPayable(): void
{
$result = $this->calculator->calculate($this->baseInput([
'valid_payment_cents' => 10000,
'other_charge_cents' => 2000,
]));
self::assertSame(8100, $result['retained_charge_cents']);
self::assertSame(1900, $result['new_refund_request_cents']);
}
public function testProrationRoundsOnceInCents(): void
{
$result = $this->calculator->calculate($this->baseInput([
'annual_fee_allocation_cents' => 10000,
'issued_book_charge_cents' => 0,
'total_instructional_weeks' => 3,
'withdrawal_request_date' => '2026-08-07',
]));
self::assertSame(3333, $result['earned_tuition_cents']);
}
public function testZeroTotalInstructionalWeeksIsRejected(): void
{
$this->expectException(InvalidArgumentException::class);
$this->calculator->calculate($this->baseInput(['total_instructional_weeks' => 0]));
}
public function testMalformedDateIsRejected(): void
{
$this->expectException(InvalidArgumentException::class);
$this->calculator->calculate($this->baseInput(['withdrawal_request_date' => '2026-02-31']));
}
public function testNoSchoolCalendarDataCannotAffectPureCalculation(): void
{
$first = $this->calculator->calculate($this->baseInput());
$second = $this->calculator->calculate($this->baseInput(['unrelated_no_school_dates' => ['2026-08-03']]));
self::assertSame($first, $second);
}
public function testNoPaymentProducesNoRefundAndFullRetainedBalance(): void
{
$result = $this->calculator->calculate($this->baseInput());
self::assertSame(0, $result['new_refund_request_cents']);
self::assertSame(6100, $result['balance_due_cents']);
}
public function testPaymentEqualToRetainedChargeProducesNeitherRefundNorBalance(): void
{
$result = $this->calculator->calculate($this->baseInput(['valid_payment_cents' => 6100]));
self::assertSame(0, $result['new_refund_request_cents']);
self::assertSame(0, $result['balance_due_cents']);
}
public function testZeroIssuedBooksProratesTheEntireAnnualAllocation(): void
{
$result = $this->calculator->calculate($this->baseInput([
'issued_book_charge_cents' => 0,
'valid_payment_cents' => 38000,
]));
self::assertSame(0, $result['issued_book_charge_cents']);
self::assertSame(1267, $result['earned_tuition_cents']);
self::assertSame(36733, $result['new_refund_request_cents']);
}
public function testFinalWeekAlwaysClosesAtTheExactAnnualAllocation(): void
{
$result = $this->calculator->calculate($this->baseInput([
'withdrawal_request_date' => '2027-02-26',
'valid_payment_cents' => 38000,
]));
self::assertSame(30, $result['studied_weeks']);
self::assertSame(33000, $result['earned_tuition_cents']);
self::assertSame(38000, $result['retained_charge_cents']);
}
public function testOpenReservationCannotMakeARefundNegative(): void
{
$result = $this->calculator->calculate($this->baseInput([
'valid_payment_cents' => 10000,
'open_reservation_cents' => 999999,
]));
self::assertSame(3900, $result['refundable_credit_cents']);
self::assertSame(0, $result['new_refund_request_cents']);
}
/** @param array<string,mixed> $overrides */
private function baseInput(array $overrides = []): array
{
return array_replace([
'annual_fee_allocation_cents' => 38000,
'issued_book_charge_cents' => 5000,
'total_instructional_weeks' => 30,
'school_year_start_date' => '2026-08-01',
'enrollment_date' => '2026-08-01',
'withdrawal_request_date' => '2026-08-07',
'valid_payment_cents' => 0,
'completed_payout_cents' => 0,
'open_reservation_cents' => 0,
'other_charge_cents' => 0,
'annual_fee_includes_books' => true,
], $overrides);
}
}