239 lines
11 KiB
PHP
239 lines
11 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\ShorakaFinancing;
|
|
use App\User;
|
|
use App\Services\ShorakaFinancingService;
|
|
use App\Http\Controllers\GadaianController;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Http\Client\Factory;
|
|
use Mockery;
|
|
use Tests\TestCase;
|
|
|
|
class ShorakaFinancingTest extends TestCase
|
|
{
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
config([
|
|
'database.default' => 'shoraka_test',
|
|
'database.connections.shoraka_test' => ['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => ''],
|
|
'api.url' => 'https://api.test', 'session.driver' => 'array',
|
|
]);
|
|
DB::purge('shoraka_test');
|
|
require_once database_path('migrations/2026_10_05_120000_create_shoraka_financings_table.php');
|
|
(new \CreateShorakaFinancingsTable())->up();
|
|
$this->actingAs(new User(['name' => 'teller1', 'cawangan' => 'KB2', 'roles' => 'teller']));
|
|
Http::swap(new Factory());
|
|
}
|
|
|
|
protected function record(): ShorakaFinancing
|
|
{
|
|
return ShorakaFinancing::create([
|
|
'kodcaw' => 'KB2', 'norujukan' => 'SAG-001', 'teller' => 'teller1',
|
|
'action' => 'gadai', 'state' => 'WAITING',
|
|
'context' => ['norujukan_gadai' => 'SAG-001', 'pinjaman' => 1000, 'cara_terima' => 'TUNAI'],
|
|
]);
|
|
}
|
|
|
|
protected function request(): Request
|
|
{
|
|
$request = new Request(['norujukan_gadai' => 'SAG-001', 'pinjaman' => 1000, '_token' => 'private']);
|
|
$request->setLaravelSession(app('session.store'));
|
|
return $request;
|
|
}
|
|
|
|
public function testHoldPersistsAndNeverSubmitsTwice(): void
|
|
{
|
|
Http::fake(function ($request) {
|
|
$this->assertSame('https://api.test/api/shoraka/gadai/KB2/SAG-001/submit', $request->url());
|
|
return Http::response(['success' => true, 'data' => ['status' => 'Active']]);
|
|
});
|
|
$service = new ShorakaFinancingService();
|
|
$service->hold($this->request(), 'SAG-001', 'gadai');
|
|
$service->hold($this->request(), 'SAG-001', 'gadai');
|
|
$this->assertSame(1, ShorakaFinancing::count());
|
|
$this->assertSame('WAITING', ShorakaFinancing::first()->state);
|
|
$this->assertArrayNotHasKey('_token', ShorakaFinancing::first()->context);
|
|
Http::assertSentCount(1);
|
|
}
|
|
|
|
public function testPendingStatusCannotTriggerPayment(): void
|
|
{
|
|
$record = $this->record();
|
|
Http::fake(function () { return Http::response(['data' => ['can_complete' => false]]); });
|
|
$controller = Mockery::mock(GadaianController::class);
|
|
$controller->shouldNotReceive('completeShorakaFinancing');
|
|
$this->app->instance(GadaianController::class, $controller);
|
|
try {
|
|
(new ShorakaFinancingService())->finish($this->request(), 'SAG-001');
|
|
$this->fail('Pending trade must block payment.');
|
|
} catch (\Symfony\Component\HttpKernel\Exception\HttpException $exception) {
|
|
$this->assertSame(409, $exception->getStatusCode());
|
|
$this->assertSame('WAITING', $record->fresh()->state);
|
|
}
|
|
}
|
|
|
|
public function testCompletedTradePaysOnlyOnce(): void
|
|
{
|
|
$record = $this->record();
|
|
Http::fake(function () { return Http::response(['data' => ['can_complete' => true, 'order_amount' => 1000]]); });
|
|
$controller = Mockery::mock(GadaianController::class);
|
|
$controller->shouldReceive('completeShorakaFinancing')->once()->andReturn(redirect('/'));
|
|
$this->app->instance(GadaianController::class, $controller);
|
|
$service = new ShorakaFinancingService();
|
|
$service->finish($this->request(), 'SAG-001');
|
|
$service->finish($this->request(), 'SAG-001');
|
|
$this->assertSame('DONE', $record->fresh()->state);
|
|
$this->assertSame([], $record->fresh()->context);
|
|
Http::assertSentCount(2);
|
|
}
|
|
|
|
public function testAmbiguousPaymentFailureRequiresReview(): void
|
|
{
|
|
$record = $this->record();
|
|
Http::fake(function () { return Http::response(['data' => ['can_complete' => true, 'order_amount' => 1000]]); });
|
|
$controller = Mockery::mock(GadaianController::class);
|
|
$controller->shouldReceive('completeShorakaFinancing')->once()->andThrow(new \RuntimeException('Timeout'));
|
|
$this->app->instance(GadaianController::class, $controller);
|
|
try {
|
|
(new ShorakaFinancingService())->finish($this->request(), 'SAG-001');
|
|
$this->fail('Expected payment failure.');
|
|
} catch (\RuntimeException $exception) {
|
|
$this->assertSame('REVIEW', $record->fresh()->state);
|
|
}
|
|
$this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class);
|
|
(new ShorakaFinancingService())->finish($this->request(), 'SAG-001');
|
|
}
|
|
|
|
public function testDifferentTellerCannotCompletePayment(): void
|
|
{
|
|
$record = $this->record();
|
|
$record->update(['teller' => 'someone-else']);
|
|
Http::fake();
|
|
try {
|
|
(new ShorakaFinancingService())->finish($this->request(), 'SAG-001');
|
|
$this->fail('Expected teller restriction.');
|
|
} catch (\Symfony\Component\HttpKernel\Exception\HttpException $exception) {
|
|
$this->assertSame(403, $exception->getStatusCode());
|
|
}
|
|
Http::assertNothingSent();
|
|
}
|
|
|
|
public function testPendingTradeCannotPrintSag(): void
|
|
{
|
|
$this->record();
|
|
Http::fake(function () { return Http::response(['data' => [
|
|
'can_complete' => false, 'order' => ['status' => 'Active'],
|
|
]]); });
|
|
$this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class);
|
|
(new ShorakaFinancingService())->printData('SAG-001');
|
|
}
|
|
|
|
public function testPrintUsesOrderCertificateAndVolume(): void
|
|
{
|
|
Http::fake(function () { return Http::response(['data' => [
|
|
'can_complete' => true, 'order' => ['status' => 'Completed'],
|
|
'details' => ['certificateDetails1' => 'CERT-01|timestamp|seller', 'unit' => 'Tonnes',
|
|
'volume' => '1.25', 'commodityType' => '000-COPPER'],
|
|
]]); });
|
|
$data = (new ShorakaFinancingService())->printData('SAG-001');
|
|
$this->assertSame('CERT-01', $data['komoditi']['no_sijil']);
|
|
$this->assertSame('1.25', $data['kuantiti']);
|
|
}
|
|
|
|
public function testBranchComesFromAuthenticatedUser(): void
|
|
{
|
|
$service = new ShorakaFinancingService();
|
|
$this->assertSame('https://api.test/api/shoraka/gadai/KB2/SAG-001', $service->url('SAG-001'));
|
|
$record = $this->record();
|
|
$record->update(['kodcaw' => 'KB3']);
|
|
$this->assertNull($service->local('SAG-001'));
|
|
}
|
|
|
|
public function testRegistrationDefersCashAndOnlinePayment(): void
|
|
{
|
|
\Illuminate\Support\Facades\Event::fake();
|
|
Http::fake(function ($request) {
|
|
if (strpos($request->url(), '/shoraka/readiness') !== false) {
|
|
return Http::response(['ready' => true]);
|
|
}
|
|
if (strpos($request->url(), '/marhun/gadai/') !== false) {
|
|
return Http::response('2000.00');
|
|
}
|
|
if (strpos($request->url(), '/gadai/norujukan/') !== false) {
|
|
return Http::response('true');
|
|
}
|
|
if (strpos($request->url(), '/shoraka/gadai/') !== false) {
|
|
return Http::response(['success' => true, 'data' => ['status' => 'Active']]);
|
|
}
|
|
$this->fail('Unexpected payment request: ' . $request->url());
|
|
});
|
|
$request = $this->request();
|
|
$request->merge([
|
|
'cara_terima' => 'TUNAI', 'keuntungan_sebulan' => 10,
|
|
'kadarkeuntungan' => 1, 'total_berat' => 5, 'nokp' => 'IC-1',
|
|
]);
|
|
$result = app(GadaianController::class)->store($request);
|
|
$this->assertStringContainsString('/shoraka/SAG-001', $result->getTargetUrl());
|
|
$this->assertSame('WAITING', ShorakaFinancing::first()->state);
|
|
app(GadaianController::class)->store($request);
|
|
Http::assertSentCount(4);
|
|
\Illuminate\Support\Facades\Event::assertNotDispatched(\App\Events\Dashboard\GadaiNotify::class);
|
|
}
|
|
|
|
public function testReadinessFailureBlocksRegistrationBeforeSave(): void
|
|
{
|
|
Http::fake(function () { return Http::response(['ready' => false]); });
|
|
try {
|
|
app(GadaianController::class)->store($this->request());
|
|
$this->fail('Registration should be blocked.');
|
|
} catch (\Symfony\Component\HttpKernel\Exception\HttpException $exception) {
|
|
$this->assertSame(503, $exception->getStatusCode());
|
|
}
|
|
Http::assertSentCount(1);
|
|
$this->assertSame(0, ShorakaFinancing::count());
|
|
}
|
|
|
|
public function testRefinancingUsesNewReferenceAndDefersCash(): void
|
|
{
|
|
\Illuminate\Support\Facades\Event::fake();
|
|
Http::fake(function ($request) {
|
|
if (strpos($request->url(), '/shoraka/readiness') !== false) {
|
|
return Http::response(['ready' => true]);
|
|
}
|
|
if (strpos($request->url(), '/updatetambahpinjaman/') !== false) {
|
|
return Http::response(['result' => 'success', 'norujukan_baru' => 'NEW-SAG']);
|
|
}
|
|
if (strpos($request->url(), '/shoraka/gadai/KB2/NEW-SAG/submit') !== false) {
|
|
return Http::response(['success' => true]);
|
|
}
|
|
$this->fail('Unexpected cash/legacy commodity request: ' . $request->url());
|
|
});
|
|
$request = $this->request();
|
|
$request->merge(['norujukan' => 'OLD-SAG']);
|
|
$request->headers->set('X-Requested-With', 'XMLHttpRequest');
|
|
$result = app(\App\Http\Controllers\PenebusanController::class)->tambahpinjaman_latest($request);
|
|
$this->assertSame(202, $result->getStatusCode());
|
|
$this->assertSame('NEW-SAG', ShorakaFinancing::first()->norujukan);
|
|
$this->assertSame('refinance', ShorakaFinancing::first()->action);
|
|
\Illuminate\Support\Facades\Event::assertNotDispatched(\App\Events\Dashboard\GadaiNotify::class);
|
|
}
|
|
|
|
public function testChangedSavedPayoutAmountCannotBePaid(): void
|
|
{
|
|
$this->record();
|
|
Http::fake(function () { return Http::response(['data' => [
|
|
'can_complete' => true, 'order_amount' => 2000,
|
|
]]); });
|
|
$controller = Mockery::mock(GadaianController::class);
|
|
$controller->shouldNotReceive('completeShorakaFinancing');
|
|
$this->app->instance(GadaianController::class, $controller);
|
|
$this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class);
|
|
(new ShorakaFinancingService())->finish($this->request(), 'SAG-001');
|
|
}
|
|
}
|