Skip to content

Commit 1bc683c

Browse files
authored
Merge pull request #6 from d-alejandro/feature/develop
Feature/develop
2 parents 0830098 + 4d8e9e4 commit 1bc683c

14 files changed

+470
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Api;
4+
5+
use App\Http\Controllers\Controller;
6+
use App\Presenters\Interfaces\OrderPresenterInterface;
7+
use App\UseCases\Interfaces\OrderDestroyUseCaseInterface;
8+
9+
final class OrderDestroyController extends Controller
10+
{
11+
public function __construct(
12+
private OrderDestroyUseCaseInterface $useCase,
13+
private OrderPresenterInterface $presenter
14+
) {
15+
}
16+
17+
public function __invoke(int $id): mixed
18+
{
19+
$response = $this->useCase->execute($id);
20+
21+
return $this->presenter->present($response);
22+
}
23+
}

app/Providers/Bindings/RepositoryServiceProvider.php

+4
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
use App\Repositories\Criteria\CriteriaApplier;
88
use App\Repositories\Criteria\Interfaces\CriteriaApplierInterface;
99
use App\Repositories\Interfaces\AgencyByNameCreatorRepositoryInterface;
10+
use App\Repositories\Interfaces\OrderDestroyRepositoryInterface;
1011
use App\Repositories\Interfaces\OrderIndexRepositoryInterface;
1112
use App\Repositories\Interfaces\OrderShowRepositoryInterface;
1213
use App\Repositories\Interfaces\OrderStoreRepositoryInterface;
14+
use App\Repositories\OrderDestroyRepository;
1315
use App\Repositories\OrderIndexRepository;
1416
use App\Repositories\OrderShowRepository;
1517
use App\Repositories\OrderStoreRepository;
@@ -42,5 +44,7 @@ public function register(): void
4244
$this->app->when(OrderShowRepository::class)
4345
->needs(CriteriaApplierInterface::class)
4446
->give(fn() => new CriteriaApplier(Order::class));
47+
48+
$this->app->bind(OrderDestroyRepositoryInterface::class, OrderDestroyRepository::class);
4549
}
4650
}

app/Providers/Bindings/UseCaseServiceProvider.php

+3
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
namespace App\Providers\Bindings;
44

5+
use App\UseCases\Interfaces\OrderDestroyUseCaseInterface;
56
use App\UseCases\Interfaces\OrderShowUseCaseInterface;
67
use App\UseCases\Interfaces\OrderStoreUseCaseInterface;
8+
use App\UseCases\OrderDestroyUseCase;
79
use App\UseCases\OrderIndexUseCase;
810
use App\UseCases\Interfaces\OrderIndexUseCaseInterface;
911
use App\UseCases\OrderShowUseCase;
@@ -17,5 +19,6 @@ public function register(): void
1719
$this->app->bind(OrderIndexUseCaseInterface::class, OrderIndexUseCase::class);
1820
$this->app->bind(OrderStoreUseCaseInterface::class, OrderStoreUseCase::class);
1921
$this->app->bind(OrderShowUseCaseInterface::class, OrderShowUseCase::class);
22+
$this->app->bind(OrderDestroyUseCaseInterface::class, OrderDestroyUseCase::class);
2023
}
2124
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace App\Repositories\Interfaces;
4+
5+
use App\DTO\Interfaces\OrderResponseDTOInterface;
6+
7+
interface OrderDestroyRepositoryInterface
8+
{
9+
public function make(OrderResponseDTOInterface $responseDTO): void;
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace App\Repositories;
4+
5+
use App\DTO\Interfaces\OrderResponseDTOInterface;
6+
use App\Repositories\Interfaces\OrderDestroyRepositoryInterface;
7+
8+
class OrderDestroyRepository implements OrderDestroyRepositoryInterface
9+
{
10+
public function make(OrderResponseDTOInterface $responseDTO): void
11+
{
12+
$responseDTO->order->delete();
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace App\UseCases\Exceptions;
4+
5+
use App\Exceptions\BaseException;
6+
7+
class OrderDestroyException extends BaseException
8+
{
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace App\UseCases\Interfaces;
4+
5+
use App\DTO\Interfaces\OrderResponseDTOInterface;
6+
7+
interface OrderDestroyUseCaseInterface
8+
{
9+
public function execute(int $id): OrderResponseDTOInterface;
10+
}

app/UseCases/OrderDestroyUseCase.php

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace App\UseCases;
4+
5+
use App\DTO\Interfaces\OrderResponseDTOInterface;
6+
use App\Repositories\Interfaces\OrderDestroyRepositoryInterface;
7+
use App\UseCases\Exceptions\OrderDestroyException;
8+
use App\UseCases\Interfaces\OrderDestroyUseCaseInterface;
9+
use App\UseCases\Interfaces\OrderShowUseCaseInterface;
10+
use Throwable;
11+
12+
class OrderDestroyUseCase implements OrderDestroyUseCaseInterface
13+
{
14+
public function __construct(
15+
private OrderShowUseCaseInterface $showUseCase,
16+
private OrderDestroyRepositoryInterface $repository
17+
) {
18+
}
19+
20+
/**
21+
* @throws OrderDestroyException
22+
*/
23+
public function execute(int $id): OrderResponseDTOInterface
24+
{
25+
try {
26+
$responseDTO = $this->showUseCase->execute($id);
27+
28+
$this->repository->make($responseDTO);
29+
30+
return $responseDTO;
31+
} catch (Throwable $exception) {
32+
throw new OrderDestroyException('Order delete error.', previous: $exception);
33+
}
34+
}
35+
}

routes/api.php

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php
22

3+
use App\Http\Controllers\Api\OrderDestroyController;
34
use App\Http\Controllers\Api\OrderIndexController;
45
use App\Http\Controllers\Api\OrderShowController;
56
use App\Http\Controllers\Api\OrderStoreController;
@@ -8,3 +9,4 @@
89
Route::get('/orders', OrderIndexController::class)->name('order.index');
910
Route::post('/orders', OrderStoreController::class)->name('order.store');
1011
Route::get('/orders/{id}', OrderShowController::class)->name('order.show');
12+
Route::delete('/orders/{id}', OrderDestroyController::class)->name('order.destroy');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
3+
namespace Tests\Feature;
4+
5+
use App\Enums\OrderStatusEnum;
6+
use App\Http\Resources\Enums\OrderResourceEnum;
7+
use App\Models\Agency;
8+
use App\Models\Enums\AgencyColumn;
9+
use App\Models\Enums\OrderColumn;
10+
use App\Models\Order;
11+
use Carbon\Carbon;
12+
use Closure;
13+
use Illuminate\Foundation\Testing\DatabaseTransactions;
14+
use Illuminate\Support\Facades\Log;
15+
use Symfony\Component\HttpFoundation\Response;
16+
use Tests\TestCase;
17+
18+
class OrderDestroyControllerTest extends TestCase
19+
{
20+
use DatabaseTransactions;
21+
22+
private const METHOD_DELETE = 'delete';
23+
24+
private Closure $route;
25+
private Order $order;
26+
private string $agencyName = 'AgencyNameTest';
27+
private OrderStatusEnum $status = OrderStatusEnum::Paid;
28+
private Carbon $rentalDate;
29+
private int $guestCount = 1;
30+
private int $transportCount = 1;
31+
private string $userName = 'UserNameTest';
32+
private string $email = 'test@test.test';
33+
private string $phone = '7000000000';
34+
private string $note = 'NoteTest';
35+
private string $adminNote = 'AdminNoteTest';
36+
37+
protected function setUp(): void
38+
{
39+
parent::setUp();
40+
41+
$this->route = fn(int $id) => route('order.destroy', ['id' => $id]);
42+
43+
$this->generateTestData();
44+
}
45+
46+
public function getDataProvider(): array
47+
{
48+
return [
49+
'single' => [
50+
'expectedResponse' => fn(Order $order, Carbon $rentalDate) => [
51+
OrderResourceEnum::Id->value => $order->getKey(),
52+
OrderResourceEnum::AgencyName->value => $this->agencyName,
53+
OrderResourceEnum::Status->value => $this->status,
54+
OrderResourceEnum::IsConfirmed->value => false,
55+
OrderResourceEnum::IsChecked->value => false,
56+
OrderResourceEnum::RentalDate->value => $rentalDate->format('d-m-Y'),
57+
OrderResourceEnum::UserName->value => $this->userName,
58+
OrderResourceEnum::TransportCount->value => $this->transportCount,
59+
OrderResourceEnum::GuestCount->value => $this->guestCount,
60+
OrderResourceEnum::AdminNote->value => $this->adminNote,
61+
OrderResourceEnum::Note->value => $this->note,
62+
OrderResourceEnum::Email->value => $this->email,
63+
OrderResourceEnum::Phone->value => $this->phone,
64+
OrderResourceEnum::ConfirmedAt->value => null,
65+
OrderResourceEnum::CreatedAt->value =>
66+
$order->getColumn(OrderColumn::CreatedAt)->format('d-m-Y H:i:s'),
67+
OrderResourceEnum::UpdatedAt->value =>
68+
$order->getColumn(OrderColumn::UpdatedAt)->format('d-m-Y H:i:s'),
69+
],
70+
],
71+
];
72+
}
73+
74+
/**
75+
* @dataProvider getDataProvider
76+
*/
77+
public function testSuccessfulExecution(Closure $expectedResponse): void
78+
{
79+
$response = $this->json(self::METHOD_DELETE, ($this->route)($this->order->getKey()));
80+
81+
$response->assertStatus(Response::HTTP_OK)
82+
->assertExactJson([
83+
'data' => $expectedResponse($this->order, $this->rentalDate),
84+
]);
85+
86+
$this->assertSoftDeleted($this->order);
87+
}
88+
89+
public function testErrorExecution(): void
90+
{
91+
Log::shouldReceive('error');
92+
93+
$id = 0;
94+
$response = $this->json(self::METHOD_DELETE, ($this->route)($id));
95+
96+
$response->assertStatus(Response::HTTP_BAD_REQUEST)
97+
->assertExactJson([
98+
'message' => 'Order delete error.',
99+
]);
100+
}
101+
102+
private function generateTestData(): void
103+
{
104+
$agency = Agency::factory()->create([
105+
AgencyColumn::Name->value => $this->agencyName,
106+
]);
107+
108+
$this->rentalDate = now()->addWeek();
109+
$this->order = Order::factory()->create([
110+
OrderColumn::AgencyId->value => $agency->getKey(),
111+
OrderColumn::Status->value => $this->status,
112+
OrderColumn::IsChecked->value => false,
113+
OrderColumn::IsConfirmed->value => false,
114+
OrderColumn::RentalDate->value => $this->rentalDate,
115+
OrderColumn::GuestCount->value => $this->guestCount,
116+
OrderColumn::TransportCount->value => $this->transportCount,
117+
OrderColumn::UserName->value => $this->userName,
118+
OrderColumn::Email->value => $this->email,
119+
OrderColumn::Phone->value => $this->phone,
120+
OrderColumn::Note->value => $this->note,
121+
OrderColumn::AdminNote->value => $this->adminNote,
122+
OrderColumn::ConfirmedAt->value => null,
123+
]);
124+
}
125+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
namespace Tests\Unit\Controller;
4+
5+
use App\DTO\OrderResponseDTO;
6+
use App\Http\Controllers\Api\OrderDestroyController;
7+
use App\Models\Order;
8+
use App\Presenters\Interfaces\OrderPresenterInterface;
9+
use App\UseCases\Interfaces\OrderDestroyUseCaseInterface;
10+
use Exception;
11+
use Illuminate\Http\JsonResponse;
12+
use Mockery;
13+
use PHPUnit\Framework\TestCase;
14+
15+
class OrderDestroyControllerTest extends TestCase
16+
{
17+
private OrderDestroyUseCaseInterface $useCaseMock;
18+
private OrderPresenterInterface $presenterMock;
19+
private OrderDestroyController $orderDestroyController;
20+
21+
protected function setUp(): void
22+
{
23+
parent::setUp();
24+
25+
$this->useCaseMock = Mockery::mock(OrderDestroyUseCaseInterface::class);
26+
$this->presenterMock = Mockery::mock(OrderPresenterInterface::class);
27+
28+
$this->orderDestroyController = new OrderDestroyController($this->useCaseMock, $this->presenterMock);
29+
}
30+
31+
protected function tearDown(): void
32+
{
33+
parent::tearDown();
34+
35+
Mockery::close();
36+
}
37+
38+
public function getDataProvider(): array
39+
{
40+
$expectedResponse = ['testKey' => 'testValue'];
41+
return [
42+
'single' => [
43+
'orderId' => 1,
44+
'responseDTO' => new OrderResponseDTO(new Order()),
45+
'presenterResponse' => new JsonResponse($expectedResponse),
46+
'expectedResponse' => $expectedResponse,
47+
],
48+
];
49+
}
50+
51+
/**
52+
* @dataProvider getDataProvider
53+
*/
54+
public function testSuccessfulOrderDestroyControllerExecution(
55+
int $orderId,
56+
OrderResponseDTO $responseDTO,
57+
JsonResponse $presenterResponse,
58+
array $expectedResponse
59+
): void {
60+
$this->useCaseMock
61+
->shouldReceive('execute')
62+
->once()
63+
->with($orderId)
64+
->andReturn($responseDTO);
65+
66+
$this->presenterMock
67+
->shouldReceive('present')
68+
->once()
69+
->with($responseDTO)
70+
->andReturn($presenterResponse);
71+
72+
$response = $this->orderDestroyController->__invoke($orderId);
73+
74+
$this->assertEquals($expectedResponse, $response->getData(true));
75+
}
76+
77+
/**
78+
* @dataProvider getDataProvider
79+
*/
80+
public function testFailedOrderDestroyUseCaseCall(int $orderId): void
81+
{
82+
$this->useCaseMock
83+
->shouldReceive('execute')
84+
->once()
85+
->andThrow(new Exception());
86+
87+
$this->presenterMock
88+
->shouldReceive('present')
89+
->never();
90+
91+
$this->expectException(Exception::class);
92+
93+
$this->orderDestroyController->__invoke($orderId);
94+
}
95+
96+
/**
97+
* @dataProvider getDataProvider
98+
*/
99+
public function testFailedOrderDestroyPresenterCall(int $orderId, OrderResponseDTO $responseDTO): void
100+
{
101+
$this->useCaseMock
102+
->shouldReceive('execute')
103+
->once()
104+
->andReturn($responseDTO);
105+
106+
$this->presenterMock
107+
->shouldReceive('present')
108+
->once()
109+
->andThrow(new Exception());
110+
111+
$this->expectException(Exception::class);
112+
113+
$this->orderDestroyController->__invoke($orderId);
114+
}
115+
}

0 commit comments

Comments
 (0)