createTenant('acme'); $user = User::query()->create([ 'nombre_apellido' => 'Grace Hopper', 'email' => 'grace@example.com', 'password' => Hash::make('secret123'), ]); $response = $this->postJson('/api/login', [ 'email' => 'grace@example.com', 'password' => 'secret123', 'tenant_codigo' => $tenant->codigo, ]); $response ->assertOk() ->assertJsonPath('message', 'Sesion iniciada correctamente.') ->assertJsonPath('token_type', 'Bearer') ->assertJsonPath('user.id', $user->id) ->assertJsonPath('user.nombre_apellido', 'Grace Hopper') ->assertJsonPath('user.email', 'grace@example.com'); $this->assertIsString($response->json('token')); $this->assertNotEmpty($response->json('token')); $accessToken = $user->tokens()->sole(); $this->assertTrue( $accessToken->expires_at->equalTo( $accessToken->created_at->copy()->addMinutes(config('sanctum.expiration')) ) ); $this->withHeader('Authorization', 'Bearer '.$response->json('token')) ->getJson('/api/me') ->assertOk() ->assertJsonPath('id', $user->id) ->assertJsonPath('email', 'grace@example.com'); } public function test_personal_access_tokens_expire_after_twelve_hours(): void { $this->travelTo(now()->startOfSecond()); $user = User::factory()->create(); $token = $user->createToken('api-token')->plainTextToken; $this->travel(12)->hours(); $this->travel(1)->minutes(); $this->withHeader('Authorization', 'Bearer '.$token) ->getJson('/api/me') ->assertUnauthorized(); } public function test_it_rejects_access_to_the_current_user_endpoint_without_a_token(): void { $this->getJson('/api/me')->assertUnauthorized(); } public function test_it_logs_out_the_current_token(): void { $user = User::factory()->create(); $token = $user->createToken('api-token')->plainTextToken; $this->withHeader('Authorization', 'Bearer '.$token) ->postJson('/api/logout') ->assertOk() ->assertJsonPath('message', 'Sesion cerrada correctamente.'); $this->assertDatabaseCount('personal_access_tokens', 0); } public function test_it_returns_a_validation_error_for_invalid_credentials(): void { $tenant = $this->createTenant('acme'); User::query()->create([ 'nombre_apellido' => 'Grace Hopper', 'email' => 'grace@example.com', 'password' => Hash::make('secret123'), ]); $this->postJson('/api/login', [ 'email' => 'grace@example.com', 'password' => 'wrong-password', 'tenant_codigo' => $tenant->codigo, ])->assertUnprocessable()->assertJsonValidationErrors(['email']); } public function test_it_validates_required_login_fields(): void { $this->postJson('/api/login', [ 'email' => 'invalid-email', 'password' => '', ])->assertUnprocessable()->assertJsonValidationErrors([ 'email', 'password', 'tenant_codigo', ]); } public function test_it_replaces_the_active_user_cart_with_the_guest_cart_on_login(): void { $tenant = $this->createTenant('acme'); $user = User::factory()->create([ 'email' => 'grace@example.com', 'password' => Hash::make('secret123'), ]); $catalogItem = $this->createCatalogItem($tenant); $userCart = Cart::query()->create([ 'tenant_codigo' => $tenant->codigo, 'user_id' => $user->id, 'status' => 'active', ]); $guestCart = Cart::query()->create([ 'tenant_codigo' => $tenant->codigo, 'guest_token' => 'guest-cart-token', 'status' => 'active', ]); $guestCart->items()->create([ 'catalog_item_id' => $catalogItem->id, 'variant_id' => null, 'cantidad' => 2, ]); $this->withCookie('guest_token', 'guest-cart-token') ->postJson('/api/login', [ 'email' => 'grace@example.com', 'password' => 'secret123', 'tenant_codigo' => $tenant->codigo, ]) ->assertOk() ->assertCookieExpired('guest_token'); $this->assertSoftDeleted('carritos', [ 'id' => $userCart->id, 'status' => 'converted', ]); $this->assertDatabaseHas('carritos', [ 'id' => $guestCart->id, 'user_id' => $user->id, 'guest_token' => null, 'status' => 'active', ]); } private function createCatalogItem(Tenant $tenant): CatalogItem { $inventory = Inventory::query()->create(['real_stock' => 10]); return CatalogItem::query()->create([ 'tenant_code' => $tenant->codigo, 'inventory_id' => $inventory->id, 'slug' => 'login-item-'.$tenant->codigo, 'nombre' => 'Login item', 'precio' => '10.00', 'inventory_policy' => InventoryPolicy::Tracked, ]); } private function createTenant(string $code): Tenant { $headerLogo = $this->createAttachment("{$code}-header"); $footerLogo = $this->createAttachment("{$code}-footer"); return Tenant::query()->create([ 'codigo' => $code, 'nombre' => ucfirst($code), 'dominio' => "{$code}.local", 'primary_color' => '#000000', 'secondary_color' => '#000000', 'danger_color' => '#000000', 'success_color' => '#000000', 'header_bg_color' => '#000000', 'footer_bg_color' => '#000000', 'header_logo_id' => $headerLogo->id, 'footer_logo_id' => $footerLogo->id, ]); } private function createAttachment(string $name): Attachment { return Attachment::query()->create([ 'path' => "test/{$name}.png", 'filename' => "{$name}.png", 'type' => AttachmentType::Image, 'mime_type' => 'image/png', ]); } }