180 lines
5.3 KiB
PHP
180 lines
5.3 KiB
PHP
<?php
|
|
|
|
namespace Tests\App\Controllers\View;
|
|
|
|
use App\Controllers\View\RefundController;
|
|
use App\Libraries\RefundEligibilityService;
|
|
use CodeIgniter\Test\CIUnitTestCase;
|
|
|
|
class RefundRequestStub
|
|
{
|
|
public function __construct(private array $post = [])
|
|
{
|
|
}
|
|
|
|
public function getPost(?string $key = null)
|
|
{
|
|
if ($key === null) {
|
|
return $this->post;
|
|
}
|
|
|
|
return $this->post[$key] ?? null;
|
|
}
|
|
|
|
public function getFile(string $name)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
class TestableRefundController extends RefundController
|
|
{
|
|
public function __construct()
|
|
{
|
|
}
|
|
|
|
public function setRequestObject($request): self
|
|
{
|
|
$this->request = $request;
|
|
return $this;
|
|
}
|
|
|
|
public function setResponseObject($response): self
|
|
{
|
|
$this->response = $response;
|
|
return $this;
|
|
}
|
|
}
|
|
|
|
class RefundEligibilityForControllerHarness extends RefundEligibilityService
|
|
{
|
|
public function __construct(private int $completedPayoutCents)
|
|
{
|
|
}
|
|
|
|
public function getCompletedPayoutTotalCentsForRefund(int $refundId): int
|
|
{
|
|
return $this->completedPayoutCents;
|
|
}
|
|
}
|
|
|
|
class RefundControllerRegressionTest extends CIUnitTestCase
|
|
{
|
|
public function testRequestRefundReadsRequestTypeFromPostBody(): void
|
|
{
|
|
$controller = $this->controllerWithPost([
|
|
'parent_id' => '123',
|
|
'amount' => '25.00',
|
|
'request_type' => 'unsupported',
|
|
]);
|
|
|
|
$response = $controller->requestRefund();
|
|
|
|
$this->assertSame(['error' => 'Invalid refund request type.'], $this->jsonResponse($response));
|
|
}
|
|
|
|
public function testPayRefundRouteArgumentIsAcceptedBeforePostRefundId(): void
|
|
{
|
|
$controller = $this->controllerWithPost([
|
|
'paid_amount' => '10.00',
|
|
'payment_method' => 'Wire',
|
|
]);
|
|
|
|
$response = $controller->payRefund(77);
|
|
|
|
$this->assertSame(['error' => 'Invalid refund payment method.'], $this->jsonResponse($response));
|
|
}
|
|
|
|
public function testCheckRefundRequiresCheckNumberBeforeDatabaseWrite(): void
|
|
{
|
|
$controller = $this->controllerWithPost([
|
|
'refund_id' => '77',
|
|
'paid_amount' => '10.00',
|
|
'payment_method' => 'Check',
|
|
]);
|
|
|
|
$response = $controller->updatePayment();
|
|
|
|
$this->assertSame(['error' => 'Check number is required for check refunds.'], $this->jsonResponse($response));
|
|
}
|
|
|
|
public function testUpdateStatusAcceptsRouteRefundId(): void
|
|
{
|
|
$controller = $this->controllerWithPost([
|
|
'status' => 'Voided',
|
|
'reason' => 'duplicate request',
|
|
]);
|
|
|
|
$response = $controller->updateStatus(77);
|
|
|
|
$this->assertSame(['error' => 'Invalid status update request.'], $this->jsonResponse($response));
|
|
}
|
|
|
|
public function testReversePayoutRequiresReasonBeforeDatabaseWrite(): void
|
|
{
|
|
$controller = $this->controllerWithPost([
|
|
'idempotency_key' => 'reverse-77',
|
|
]);
|
|
|
|
$response = $controller->reversePayout(77);
|
|
|
|
$this->assertSame(['error' => 'Reversal reason is required.'], $this->jsonResponse($response));
|
|
}
|
|
|
|
public function testReversePayoutRequiresIdempotencyKeyBeforeDatabaseWrite(): void
|
|
{
|
|
$controller = $this->controllerWithPost([
|
|
'reason' => 'wrong payout amount',
|
|
]);
|
|
|
|
$response = $controller->reversePayout(77);
|
|
|
|
$this->assertSame(['error' => 'Missing reversal idempotency key.'], $this->jsonResponse($response));
|
|
}
|
|
|
|
public function testReversePayoutRejectsNonPositiveAmountBeforeDatabaseWrite(): void
|
|
{
|
|
$controller = $this->controllerWithPost([
|
|
'reason' => 'wrong payout amount',
|
|
'idempotency_key' => 'reverse-77',
|
|
'amount' => '0.00',
|
|
]);
|
|
|
|
$response = $controller->reversePayout(77);
|
|
|
|
$this->assertSame(['error' => 'Reversal amount must be greater than zero.'], $this->jsonResponse($response));
|
|
}
|
|
|
|
public function testApprovedRefundRecalculationCannotDropBelowCompletedPayouts(): void
|
|
{
|
|
$controller = $this->controllerWithPost([]);
|
|
$property = (new \ReflectionClass(RefundController::class))->getProperty('refundEligibilityService');
|
|
$property->setAccessible(true);
|
|
$property->setValue($controller, new RefundEligibilityForControllerHarness(2500));
|
|
|
|
$method = (new \ReflectionClass(RefundController::class))->getMethod('buildRefundRecalculationUpdate');
|
|
$method->setAccessible(true);
|
|
$update = $method->invoke($controller, [
|
|
'id' => 55,
|
|
'status' => 'Approved',
|
|
], 10.00);
|
|
|
|
$this->assertEqualsWithDelta(25.0, (float)$update['refund_amount'], 0.0001);
|
|
$this->assertSame(2500, $update['approved_amount_cents']);
|
|
$this->assertSame('requires_review', $update['reconciliation_status']);
|
|
$this->assertStringContainsString('Completed payouts', $update['reconciliation_reason']);
|
|
}
|
|
|
|
private function controllerWithPost(array $post): TestableRefundController
|
|
{
|
|
return (new TestableRefundController())
|
|
->setRequestObject(new RefundRequestStub($post))
|
|
->setResponseObject(service('response'));
|
|
}
|
|
|
|
private function jsonResponse($response): array
|
|
{
|
|
return json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR);
|
|
}
|
|
}
|