feat(scanner): refactor ticket scanning endpoint to accept UUID in request body and add validation
This commit is contained in:
@@ -6086,11 +6086,16 @@
|
|||||||
"key": "Accept",
|
"key": "Accept",
|
||||||
"value": "application/json",
|
"value": "application/json",
|
||||||
"type": "text"
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json",
|
||||||
|
"type": "text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Ruta Laravel: `POST /api/v1/scanner/tickets/{ticketUuid}/scan`\n\nControlador: `App\\Domains\\Ticket\\Controllers\\Scanner\\TicketController@scan`\n\nRequiere autenticación Sanctum.",
|
"description": "Ruta Laravel: `POST /api/v1/scanner/tickets/scan`\n\nControlador: `App\\Domains\\Ticket\\Controllers\\Scanner\\TicketController@scan`\n\nRequiere autenticación Sanctum.",
|
||||||
"url": {
|
"url": {
|
||||||
"raw": "{{base_url}}/api/v1/scanner/tickets/{{ticket_uuid}}/scan",
|
"raw": "{{base_url}}/api/v1/scanner/tickets/scan",
|
||||||
"host": [
|
"host": [
|
||||||
"{{base_url}}"
|
"{{base_url}}"
|
||||||
],
|
],
|
||||||
@@ -6099,10 +6104,18 @@
|
|||||||
"v1",
|
"v1",
|
||||||
"scanner",
|
"scanner",
|
||||||
"tickets",
|
"tickets",
|
||||||
"{{ticket_uuid}}",
|
|
||||||
"scan"
|
"scan"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"data\": \"{{ticket_uuid}}\"\n}",
|
||||||
|
"options": {
|
||||||
|
"raw": {
|
||||||
|
"language": "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"type": "bearer",
|
"type": "bearer",
|
||||||
"bearer": [
|
"bearer": [
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Domains\Ticket\Controllers\Scanner;
|
|||||||
|
|
||||||
use App\Domains\Auth\Models\User;
|
use App\Domains\Auth\Models\User;
|
||||||
use App\Domains\Ticket\Requests\ScannerTicketIndexRequest;
|
use App\Domains\Ticket\Requests\ScannerTicketIndexRequest;
|
||||||
|
use App\Domains\Ticket\Requests\ScanTicketRequest;
|
||||||
use App\Domains\Ticket\Resources\Scanner\ScannedTicketResource;
|
use App\Domains\Ticket\Resources\Scanner\ScannedTicketResource;
|
||||||
use App\Domains\Ticket\Resources\TicketResource;
|
use App\Domains\Ticket\Resources\TicketResource;
|
||||||
use App\Domains\Ticket\Services\ScannerTicketService;
|
use App\Domains\Ticket\Services\ScannerTicketService;
|
||||||
@@ -35,13 +36,13 @@ class TicketController extends Controller
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function scan(Request $request, string $ticketUuid): TicketResource
|
public function scan(ScanTicketRequest $request): TicketResource
|
||||||
{
|
{
|
||||||
/** @var User $scanner */
|
/** @var User $scanner */
|
||||||
$scanner = $request->user();
|
$scanner = $request->user();
|
||||||
|
|
||||||
return TicketResource::make(
|
return TicketResource::make(
|
||||||
$this->ticketService->scan($scanner, $ticketUuid)
|
$this->ticketService->scan($scanner, $request->validated('data'))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
app/Domains/Ticket/Requests/ScanTicketRequest.php
Normal file
30
app/Domains/Ticket/Requests/ScanTicketRequest.php
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Ticket\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class ScanTicketRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<string, list<string>> */
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'data' => ['required', 'uuid'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<string, string> */
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'data.required' => 'El QR proporcionado es inválido.',
|
||||||
|
'data.uuid' => 'El QR proporcionado es inválido.',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,8 +7,7 @@ Route::prefix('v1/scanner/tickets')
|
|||||||
->middleware(['auth:sanctum', 'scanner.tenant'])
|
->middleware(['auth:sanctum', 'scanner.tenant'])
|
||||||
->group(function (): void {
|
->group(function (): void {
|
||||||
Route::get('/', [TicketController::class, 'index']);
|
Route::get('/', [TicketController::class, 'index']);
|
||||||
|
Route::post('scan', [TicketController::class, 'scan']);
|
||||||
Route::get('{ticketUuid}', [TicketController::class, 'show'])
|
Route::get('{ticketUuid}', [TicketController::class, 'show'])
|
||||||
->whereUuid('ticketUuid');
|
->whereUuid('ticketUuid');
|
||||||
Route::post('{ticketUuid}/scan', [TicketController::class, 'scan'])
|
|
||||||
->whereUuid('ticketUuid');
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ function bodyFor(string $method, string $uri): ?array
|
|||||||
'POST api/v1/scanner/password/reset-attempts' => ['email' => '{{scanner_email}}'],
|
'POST api/v1/scanner/password/reset-attempts' => ['email' => '{{scanner_email}}'],
|
||||||
'POST api/v1/scanner/password/reset-attempts/validate' => ['email' => '{{scanner_email}}', 'codigo' => '{{reset_code}}'],
|
'POST api/v1/scanner/password/reset-attempts/validate' => ['email' => '{{scanner_email}}', 'codigo' => '{{reset_code}}'],
|
||||||
'POST api/v1/scanner/password/reset' => ['email' => '{{scanner_email}}', 'codigo' => '{{reset_code}}', 'password' => '{{scanner_password}}', 'password_confirmation' => '{{scanner_password}}'],
|
'POST api/v1/scanner/password/reset' => ['email' => '{{scanner_email}}', 'codigo' => '{{reset_code}}', 'password' => '{{scanner_password}}', 'password_confirmation' => '{{scanner_password}}'],
|
||||||
|
'POST api/v1/scanner/tickets/scan' => ['data' => '{{ticket_uuid}}'],
|
||||||
];
|
];
|
||||||
|
|
||||||
if (isset($exact[$key])) {
|
if (isset($exact[$key])) {
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class PrepareLoadTestTicketsCommandTest extends TestCase
|
|||||||
|
|
||||||
$first = $rows[0];
|
$first = $rows[0];
|
||||||
$this->withToken($first['scanner_token'])
|
$this->withToken($first['scanner_token'])
|
||||||
->postJson("/api/v1/scanner/tickets/{$first['ticket_uuid']}/scan")
|
->postJson('/api/v1/scanner/tickets/scan', ['data' => $first['ticket_uuid']])
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertJsonPath('data.ticket', $first['ticket_uuid']);
|
->assertJsonPath('data.ticket', $first['ticket_uuid']);
|
||||||
|
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ class ScannerTicketControllerTest extends TestCase
|
|||||||
$ticket = $this->createTicket('77777777-7777-4777-8777-777777777777');
|
$ticket = $this->createTicket('77777777-7777-4777-8777-777777777777');
|
||||||
Sanctum::actingAs($this->scanner);
|
Sanctum::actingAs($this->scanner);
|
||||||
|
|
||||||
$this->postJson("/api/v1/scanner/tickets/{$ticket->ticket}/scan")
|
$this->postJson('/api/v1/scanner/tickets/scan', ['data' => $ticket->ticket])
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertJsonPath('data.ticket', $ticket->ticket)
|
->assertJsonPath('data.ticket', $ticket->ticket)
|
||||||
->assertJsonPath('data.scanner_user_id', $this->scanner->id)
|
->assertJsonPath('data.scanner_user_id', $this->scanner->id)
|
||||||
@@ -217,11 +217,26 @@ class ScannerTicketControllerTest extends TestCase
|
|||||||
$this->assertNotNull($ticket->fresh()->used_at);
|
$this->assertNotNull($ticket->fresh()->used_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_scan_requires_a_uuid_in_the_data_field(): void
|
||||||
|
{
|
||||||
|
Sanctum::actingAs($this->scanner);
|
||||||
|
|
||||||
|
$this->postJson('/api/v1/scanner/tickets/scan')
|
||||||
|
->assertUnprocessable()
|
||||||
|
->assertJsonPath('message', 'El QR proporcionado es inválido.')
|
||||||
|
->assertJsonPath('errors.data.0', 'El QR proporcionado es inválido.');
|
||||||
|
|
||||||
|
$this->postJson('/api/v1/scanner/tickets/scan', ['data' => 'not-a-uuid'])
|
||||||
|
->assertUnprocessable()
|
||||||
|
->assertJsonPath('message', 'El QR proporcionado es inválido.')
|
||||||
|
->assertJsonPath('errors.data.0', 'El QR proporcionado es inválido.');
|
||||||
|
}
|
||||||
|
|
||||||
public function test_ticket_cannot_be_scanned_twice(): void
|
public function test_ticket_cannot_be_scanned_twice(): void
|
||||||
{
|
{
|
||||||
$ticket = $this->createTicket('88888888-8888-4888-8888-888888888888');
|
$ticket = $this->createTicket('88888888-8888-4888-8888-888888888888');
|
||||||
Sanctum::actingAs($this->scanner);
|
Sanctum::actingAs($this->scanner);
|
||||||
$this->postJson("/api/v1/scanner/tickets/{$ticket->ticket}/scan")->assertOk();
|
$this->postJson('/api/v1/scanner/tickets/scan', ['data' => $ticket->ticket])->assertOk();
|
||||||
|
|
||||||
$otherScanner = User::factory()->create([
|
$otherScanner = User::factory()->create([
|
||||||
'rol_codigo' => RoleCode::Scanner->value,
|
'rol_codigo' => RoleCode::Scanner->value,
|
||||||
@@ -230,7 +245,7 @@ class ScannerTicketControllerTest extends TestCase
|
|||||||
$otherScanner->scanCategories()->attach($this->category);
|
$otherScanner->scanCategories()->attach($this->category);
|
||||||
Sanctum::actingAs($otherScanner);
|
Sanctum::actingAs($otherScanner);
|
||||||
|
|
||||||
$this->postJson("/api/v1/scanner/tickets/{$ticket->ticket}/scan")
|
$this->postJson('/api/v1/scanner/tickets/scan', ['data' => $ticket->ticket])
|
||||||
->assertUnprocessable()
|
->assertUnprocessable()
|
||||||
->assertJsonValidationErrors('ticket');
|
->assertJsonValidationErrors('ticket');
|
||||||
$this->assertSame($this->scanner->id, $ticket->fresh()->scanner_user_id);
|
$this->assertSame($this->scanner->id, $ticket->fresh()->scanner_user_id);
|
||||||
@@ -249,7 +264,7 @@ class ScannerTicketControllerTest extends TestCase
|
|||||||
);
|
);
|
||||||
Sanctum::actingAs($this->scanner);
|
Sanctum::actingAs($this->scanner);
|
||||||
|
|
||||||
$this->postJson("/api/v1/scanner/tickets/{$ticket->ticket}/scan")
|
$this->postJson('/api/v1/scanner/tickets/scan', ['data' => $ticket->ticket])
|
||||||
->assertUnprocessable()
|
->assertUnprocessable()
|
||||||
->assertJsonValidationErrors('ticket');
|
->assertJsonValidationErrors('ticket');
|
||||||
$this->assertNull($ticket->fresh()->used_at);
|
$this->assertNull($ticket->fresh()->used_at);
|
||||||
@@ -274,7 +289,7 @@ class ScannerTicketControllerTest extends TestCase
|
|||||||
->assertOk()
|
->assertOk()
|
||||||
->assertJsonPath('data.id', $ticket->id);
|
->assertJsonPath('data.id', $ticket->id);
|
||||||
|
|
||||||
$this->postJson("/api/v1/scanner/tickets/{$ticket->ticket}/scan")
|
$this->postJson('/api/v1/scanner/tickets/scan', ['data' => $ticket->ticket])
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertJsonPath('data.scanner_user_id', $this->scanner->id);
|
->assertJsonPath('data.scanner_user_id', $this->scanner->id);
|
||||||
}
|
}
|
||||||
@@ -297,7 +312,7 @@ class ScannerTicketControllerTest extends TestCase
|
|||||||
|
|
||||||
$this->getJson("/api/v1/scanner/tickets/{$foreignTicket->ticket}")
|
$this->getJson("/api/v1/scanner/tickets/{$foreignTicket->ticket}")
|
||||||
->assertNotFound();
|
->assertNotFound();
|
||||||
$this->postJson("/api/v1/scanner/tickets/{$foreignTicket->ticket}/scan")
|
$this->postJson('/api/v1/scanner/tickets/scan', ['data' => $foreignTicket->ticket])
|
||||||
->assertNotFound();
|
->assertNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,7 +328,7 @@ class ScannerTicketControllerTest extends TestCase
|
|||||||
|
|
||||||
$this->getJson('/api/v1/scanner/tickets')->assertForbidden();
|
$this->getJson('/api/v1/scanner/tickets')->assertForbidden();
|
||||||
$this->getJson("/api/v1/scanner/tickets/{$ticket->ticket}")->assertForbidden();
|
$this->getJson("/api/v1/scanner/tickets/{$ticket->ticket}")->assertForbidden();
|
||||||
$this->postJson("/api/v1/scanner/tickets/{$ticket->ticket}/scan")->assertForbidden();
|
$this->postJson('/api/v1/scanner/tickets/scan', ['data' => $ticket->ticket])->assertForbidden();
|
||||||
$this->assertNull($ticket->fresh()->used_at);
|
$this->assertNull($ticket->fresh()->used_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user