diff --git a/ShopIt_API_Postman_Collection.json b/ShopIt_API_Postman_Collection.json index b895726..efad4b2 100644 --- a/ShopIt_API_Postman_Collection.json +++ b/ShopIt_API_Postman_Collection.json @@ -6086,11 +6086,16 @@ "key": "Accept", "value": "application/json", "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": { - "raw": "{{base_url}}/api/v1/scanner/tickets/{{ticket_uuid}}/scan", + "raw": "{{base_url}}/api/v1/scanner/tickets/scan", "host": [ "{{base_url}}" ], @@ -6099,10 +6104,18 @@ "v1", "scanner", "tickets", - "{{ticket_uuid}}", "scan" ] }, + "body": { + "mode": "raw", + "raw": "{\n \"data\": \"{{ticket_uuid}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "auth": { "type": "bearer", "bearer": [ diff --git a/app/Domains/Ticket/Controllers/Scanner/TicketController.php b/app/Domains/Ticket/Controllers/Scanner/TicketController.php index fde6b2a..23478d0 100644 --- a/app/Domains/Ticket/Controllers/Scanner/TicketController.php +++ b/app/Domains/Ticket/Controllers/Scanner/TicketController.php @@ -4,6 +4,7 @@ namespace App\Domains\Ticket\Controllers\Scanner; use App\Domains\Auth\Models\User; use App\Domains\Ticket\Requests\ScannerTicketIndexRequest; +use App\Domains\Ticket\Requests\ScanTicketRequest; use App\Domains\Ticket\Resources\Scanner\ScannedTicketResource; use App\Domains\Ticket\Resources\TicketResource; 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 */ $scanner = $request->user(); return TicketResource::make( - $this->ticketService->scan($scanner, $ticketUuid) + $this->ticketService->scan($scanner, $request->validated('data')) ); } } diff --git a/app/Domains/Ticket/Requests/ScanTicketRequest.php b/app/Domains/Ticket/Requests/ScanTicketRequest.php new file mode 100644 index 0000000..13a590e --- /dev/null +++ b/app/Domains/Ticket/Requests/ScanTicketRequest.php @@ -0,0 +1,30 @@ +> */ + public function rules(): array + { + return [ + 'data' => ['required', 'uuid'], + ]; + } + + /** @return array */ + public function messages(): array + { + return [ + 'data.required' => 'El QR proporcionado es inválido.', + 'data.uuid' => 'El QR proporcionado es inválido.', + ]; + } +} diff --git a/app/Domains/Ticket/routes/scanner.php b/app/Domains/Ticket/routes/scanner.php index 383bf6b..7a9fb2b 100644 --- a/app/Domains/Ticket/routes/scanner.php +++ b/app/Domains/Ticket/routes/scanner.php @@ -7,8 +7,7 @@ Route::prefix('v1/scanner/tickets') ->middleware(['auth:sanctum', 'scanner.tenant']) ->group(function (): void { Route::get('/', [TicketController::class, 'index']); + Route::post('scan', [TicketController::class, 'scan']); Route::get('{ticketUuid}', [TicketController::class, 'show']) ->whereUuid('ticketUuid'); - Route::post('{ticketUuid}/scan', [TicketController::class, 'scan']) - ->whereUuid('ticketUuid'); }); diff --git a/postman/generate-shopit-collection.php b/postman/generate-shopit-collection.php index ad2498e..8adebaa 100644 --- a/postman/generate-shopit-collection.php +++ b/postman/generate-shopit-collection.php @@ -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/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/tickets/scan' => ['data' => '{{ticket_uuid}}'], ]; if (isset($exact[$key])) { diff --git a/tests/Feature/Ticket/PrepareLoadTestTicketsCommandTest.php b/tests/Feature/Ticket/PrepareLoadTestTicketsCommandTest.php index b7e2867..0124460 100644 --- a/tests/Feature/Ticket/PrepareLoadTestTicketsCommandTest.php +++ b/tests/Feature/Ticket/PrepareLoadTestTicketsCommandTest.php @@ -68,7 +68,7 @@ class PrepareLoadTestTicketsCommandTest extends TestCase $first = $rows[0]; $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() ->assertJsonPath('data.ticket', $first['ticket_uuid']); diff --git a/tests/Feature/Ticket/ScannerTicketControllerTest.php b/tests/Feature/Ticket/ScannerTicketControllerTest.php index 0ed42df..2472abc 100644 --- a/tests/Feature/Ticket/ScannerTicketControllerTest.php +++ b/tests/Feature/Ticket/ScannerTicketControllerTest.php @@ -203,7 +203,7 @@ class ScannerTicketControllerTest extends TestCase $ticket = $this->createTicket('77777777-7777-4777-8777-777777777777'); Sanctum::actingAs($this->scanner); - $this->postJson("/api/v1/scanner/tickets/{$ticket->ticket}/scan") + $this->postJson('/api/v1/scanner/tickets/scan', ['data' => $ticket->ticket]) ->assertOk() ->assertJsonPath('data.ticket', $ticket->ticket) ->assertJsonPath('data.scanner_user_id', $this->scanner->id) @@ -217,11 +217,26 @@ class ScannerTicketControllerTest extends TestCase $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 { $ticket = $this->createTicket('88888888-8888-4888-8888-888888888888'); 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([ 'rol_codigo' => RoleCode::Scanner->value, @@ -230,7 +245,7 @@ class ScannerTicketControllerTest extends TestCase $otherScanner->scanCategories()->attach($this->category); Sanctum::actingAs($otherScanner); - $this->postJson("/api/v1/scanner/tickets/{$ticket->ticket}/scan") + $this->postJson('/api/v1/scanner/tickets/scan', ['data' => $ticket->ticket]) ->assertUnprocessable() ->assertJsonValidationErrors('ticket'); $this->assertSame($this->scanner->id, $ticket->fresh()->scanner_user_id); @@ -249,7 +264,7 @@ class ScannerTicketControllerTest extends TestCase ); Sanctum::actingAs($this->scanner); - $this->postJson("/api/v1/scanner/tickets/{$ticket->ticket}/scan") + $this->postJson('/api/v1/scanner/tickets/scan', ['data' => $ticket->ticket]) ->assertUnprocessable() ->assertJsonValidationErrors('ticket'); $this->assertNull($ticket->fresh()->used_at); @@ -274,7 +289,7 @@ class ScannerTicketControllerTest extends TestCase ->assertOk() ->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() ->assertJsonPath('data.scanner_user_id', $this->scanner->id); } @@ -297,7 +312,7 @@ class ScannerTicketControllerTest extends TestCase $this->getJson("/api/v1/scanner/tickets/{$foreignTicket->ticket}") ->assertNotFound(); - $this->postJson("/api/v1/scanner/tickets/{$foreignTicket->ticket}/scan") + $this->postJson('/api/v1/scanner/tickets/scan', ['data' => $foreignTicket->ticket]) ->assertNotFound(); } @@ -313,7 +328,7 @@ class ScannerTicketControllerTest extends TestCase $this->getJson('/api/v1/scanner/tickets')->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); }