createTenant('sonder', 'Sonder', 'sonder.com.ar'); $user = User::factory()->create([ 'email' => 'buyer@example.com', ]); $category = \App\Domains\Catalog\Models\Category::query()->create([ 'tenant_code' => 'sonder', 'nombre' => 'Test Category', ]); $product = Product::query()->create([ 'tenant_codigo' => 'sonder', 'categoria_id' => $category->id, 'slug' => 'test-product', 'nombre' => 'Test Product', 'descripcion' => 'Test', 'precio' => '50.00', ]); $variant = ProductVariant::query()->create([ 'producto_id' => $product->id, 'slug' => 'test-variant', 'nombre' => 'Test Variant', 'stock' => 10, 'precio' => '50.00', ]); $cartResponse = $this->actingAs($user, 'sanctum') ->postJson('/api/tenants/sonder/cart/items', [ 'product_variant_id' => $variant->id, 'cantidad' => 2, ]) ->assertOk(); $cartId = $cartResponse->json('data.id'); $this->assertDatabaseHas('carritos', [ 'id' => $cartId, 'tenant_codigo' => 'sonder', 'user_id' => $user->id, 'status' => 'active', ]); $response = $this->actingAs($user, 'sanctum') ->postJson('/api/tenants/sonder/compras', [ 'cart_id' => $cartId, 'dni' => '987654321', 'telefono' => '+54 9 341 555-4321', 'nombre_apellido' => 'Juan Perez', 'email' => 'juan.perez@example.com', ]); $response->assertCreated(); $response->assertJsonPath('data.cart_id', $cartId); $response->assertJsonPath('data.dni', '987654321'); $response->assertJsonPath('data.telefono', '+54 9 341 555-4321'); $response->assertJsonPath('data.nombre_apellido', 'Juan Perez'); $response->assertJsonPath('data.email', 'juan.perez@example.com'); $response->assertJsonPath('data.tenant_codigo', 'sonder'); $response->assertJsonPath('data.status', Purchase::STATUS_CREATED); $response->assertJsonPath('data.items', []); $response->assertJsonPath('data.subtotal', '100.00'); $response->assertJsonPath('data.total', '100.00'); $purchaseId = $response->json('data.id'); $this->assertDatabaseHas('compras', [ 'id' => $purchaseId, 'cart_id' => $cartId, 'tenant_codigo' => 'sonder', 'user_id' => $user->id, 'dni' => '987654321', 'telefono' => '+54 9 341 555-4321', 'nombre_apellido' => 'Juan Perez', 'email' => 'juan.perez@example.com', 'status' => Purchase::STATUS_CREATED, 'total' => 100, ]); $this->assertDatabaseMissing('compra_items', [ 'compra_id' => $purchaseId, ]); $this->assertDatabaseHas('carritos', [ 'id' => $cartId, 'status' => 'active', 'user_id' => $user->id, ]); $this->assertDatabaseHas('carrito_items', [ 'cart_id' => $cartId, 'producto_variante_id' => $variant->id, 'cantidad' => 2, ]); $this->assertDatabaseHas('productos_variantes', [ 'id' => $variant->id, 'stock_real' => 10, 'stock_reservado' => 2, ]); } public function test_it_moves_a_created_purchase_to_pending_payment_when_finalized(): void { $tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar'); $user = User::factory()->create([ 'email' => 'buyer@example.com', ]); $variant = $this->createVariantForTenant('sonder', 10, '50.00'); $cartId = $this->actingAs($user, 'sanctum') ->postJson('/api/tenants/sonder/cart/items', [ 'product_variant_id' => $variant->id, 'cantidad' => 2, ]) ->assertOk() ->json('data.id'); $purchaseId = $this->actingAs($user, 'sanctum') ->postJson('/api/tenants/sonder/compras', [ 'cart_id' => $cartId, 'dni' => '987654321', 'telefono' => '+54 9 341 555-4321', 'nombre_apellido' => 'Juan Perez', 'email' => 'juan.perez@example.com', ]) ->assertCreated() ->json('data.id'); Purchase::query() ->whereKey($purchaseId) ->update([ 'payment_method' => 'transfer', ]); $this->actingAs($user, 'sanctum') ->postJson("/api/tenants/sonder/compras/{$purchaseId}/finalize") ->assertOk() ->assertJsonPath('data.status', Purchase::STATUS_PENDING_PAYMENT); $this->assertDatabaseHas('compras', [ 'id' => $purchaseId, 'status' => Purchase::STATUS_PENDING_PAYMENT, 'payment_method' => 'transfer', ]); } public function test_it_rejects_a_cart_from_another_user(): void { $this->createTenant('sonder', 'Sonder', 'sonder.com.ar'); $owner = User::factory()->create(); $attacker = User::factory()->create(); $variant = $this->createVariantForTenant('sonder', 10, '50.00'); $cartId = $this->actingAs($owner, 'sanctum') ->postJson('/api/tenants/sonder/cart/items', [ 'product_variant_id' => $variant->id, 'cantidad' => 1, ]) ->assertOk() ->json('data.id'); $this->actingAs($attacker, 'sanctum') ->postJson('/api/tenants/sonder/compras', [ 'cart_id' => $cartId, 'dni' => '12345678', 'telefono' => '+54 9 341 555-1111', 'nombre_apellido' => 'Intruso', 'email' => 'intruso@example.com', ]) ->assertNotFound(); } public function test_it_rejects_a_cart_from_another_tenant(): void { $this->createTenant('sonder', 'Sonder', 'sonder.com.ar'); $this->createTenant('globex', 'Globex', 'globex.com.ar'); $user = User::factory()->create(); $variant = $this->createVariantForTenant('globex', 10, '50.00'); $cartId = $this->actingAs($user, 'sanctum') ->postJson('/api/tenants/globex/cart/items', [ 'product_variant_id' => $variant->id, 'cantidad' => 1, ]) ->assertOk() ->json('data.id'); $this->actingAs($user, 'sanctum') ->postJson('/api/tenants/sonder/compras', [ 'cart_id' => $cartId, 'dni' => '12345678', 'telefono' => '+54 9 341 555-1111', 'nombre_apellido' => 'Juan Perez', 'email' => 'juan.perez@example.com', ]) ->assertNotFound(); } public function test_it_rejects_an_empty_cart(): void { $this->createTenant('sonder', 'Sonder', 'sonder.com.ar'); $user = User::factory()->create(); $cart = Cart::query()->create([ 'tenant_codigo' => 'sonder', 'user_id' => $user->id, 'status' => 'active', ]); $this->actingAs($user, 'sanctum') ->postJson('/api/tenants/sonder/compras', [ 'cart_id' => $cart->id, 'dni' => '12345678', 'telefono' => '+54 9 341 555-1111', 'nombre_apellido' => 'Juan Perez', 'email' => 'juan.perez@example.com', ]) ->assertUnprocessable() ->assertJsonValidationErrors(['cart_id']); } public function test_it_rejects_a_converted_cart(): void { $this->createTenant('sonder', 'Sonder', 'sonder.com.ar'); $user = User::factory()->create(); $cart = Cart::query()->create([ 'tenant_codigo' => 'sonder', 'user_id' => $user->id, 'status' => 'converted', ]); $this->actingAs($user, 'sanctum') ->postJson('/api/tenants/sonder/compras', [ 'cart_id' => $cart->id, 'dni' => '12345678', 'telefono' => '+54 9 341 555-1111', 'nombre_apellido' => 'Juan Perez', 'email' => 'juan.perez@example.com', ]) ->assertUnprocessable() ->assertJsonValidationErrors(['cart_id']); } protected function createVariantForTenant( string $tenantCode, int $stock, string $price, string $slugPrefix = 'shirt', ): ProductVariant { $tenant = Tenant::query()->where('codigo', $tenantCode)->first(); if (! $tenant) { $this->createTenant($tenantCode, ucfirst($tenantCode), "{$tenantCode}.com"); } $category = \App\Domains\Catalog\Models\Category::query()->create([ 'tenant_code' => $tenantCode, 'nombre' => "{$slugPrefix} category {$tenantCode}", ]); $product = Product::query()->create([ 'tenant_codigo' => $tenantCode, 'categoria_id' => $category->id, 'slug' => "{$slugPrefix}-{$tenantCode}-".Product::query()->count(), 'nombre' => ucfirst($slugPrefix)." {$tenantCode}", 'descripcion' => 'Test product', 'precio' => $price, ]); return ProductVariant::query()->create([ 'producto_id' => $product->id, 'slug' => "{$slugPrefix}-variant-".ProductVariant::query()->count(), 'nombre' => ucfirst($slugPrefix).' Variant', 'stock' => $stock, 'descripcion' => 'Test variant', 'precio' => $price, ])->load('product'); } protected function createTenant(string $codigo, string $nombre, string $dominio): Tenant { $hdrKey = (string) \Illuminate\Support\Str::uuid(); $ftrKey = (string) \Illuminate\Support\Str::uuid(); $headerAttachment = \App\Domains\Attachable\Models\Attachment::create([ 'key' => $hdrKey, 'path' => 'tenants/' . $hdrKey . '.png', 'filename' => 'logo_header.png', 'type' => \App\Domains\Attachable\Enums\AttachmentType::Image, 'mime_type' => 'image/png', ]); $footerAttachment = \App\Domains\Attachable\Models\Attachment::create([ 'key' => $ftrKey, 'path' => 'tenants/' . $ftrKey . '.png', 'filename' => 'logo_footer.png', 'type' => \App\Domains\Attachable\Enums\AttachmentType::Image, 'mime_type' => 'image/png', ]); return Tenant::create([ 'codigo' => $codigo, 'nombre' => $nombre, 'dominio' => $dominio, 'primary_color' => '#111111', 'secondary_color' => '#222222', 'danger_color' => '#333333', 'success_color' => '#28a745', 'header_bg_color' => '#444444', 'footer_bg_color' => '#444444', 'header_logo_id' => $headerAttachment->id, 'footer_logo_id' => $footerAttachment->id, ]); } }