feat(desfile): implement entry management with image handling and variant synchronization
This commit is contained in:
258
tests/Feature/Desfile/EntryControllerTest.php
Normal file
258
tests/Feature/Desfile/EntryControllerTest.php
Normal file
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Desfile;
|
||||
|
||||
use App\Domains\Attachable\Enums\AttachmentType;
|
||||
use App\Domains\Attachable\Models\Attachment;
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Authorization\Enums\RoleCode;
|
||||
use App\Domains\Catalog\Enums\InventoryPolicy;
|
||||
use App\Domains\Catalog\Enums\InventorySubject;
|
||||
use App\Domains\Catalog\Models\Attribute;
|
||||
use App\Domains\Catalog\Models\CatalogItem;
|
||||
use App\Domains\Catalog\Models\Inventory;
|
||||
use App\Domains\Catalog\Models\ItemAttribute;
|
||||
use App\Domains\Catalog\Models\Variant;
|
||||
use App\Domains\Menu\Models\Menu;
|
||||
use App\Domains\Shared\Enums\FieldType;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use App\Domains\Tenant\Models\WebsiteType;
|
||||
use Database\Seeders\AuthorizationSeeder;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
use Tests\TestCase;
|
||||
|
||||
class EntryControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->seed(AuthorizationSeeder::class);
|
||||
WebsiteType::query()->create([
|
||||
'codigo' => 'onticket',
|
||||
'nombre' => 'OnTicket',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_it_returns_and_synchronizes_the_single_entry_product_variants(): void
|
||||
{
|
||||
[$tenant, $entry] = $this->configuredEntry();
|
||||
$existing = $this->createVariant($entry, 'NORMAL', 'A', '1', '1', 100000);
|
||||
Sanctum::actingAs($this->createAdminAppUser($tenant));
|
||||
|
||||
$this->getJson('/api/v1/adminapp/tenant/desfile/entries')
|
||||
->assertOk()
|
||||
->assertJsonPath('data.id', $entry->id)
|
||||
->assertJsonPath('data.variants.0.id', $existing->id)
|
||||
->assertJsonPath('data.variants.0.type', 'NORMAL')
|
||||
->assertJsonPath('data.variants.0.seat', '1')
|
||||
->assertJsonPath('data.variants.0.price', '100000.00');
|
||||
|
||||
$response = $this->putJson('/api/v1/adminapp/tenant/desfile/entries', [
|
||||
'variants' => [
|
||||
[
|
||||
'id' => $existing->id,
|
||||
'type' => 'NORMAL',
|
||||
'sector' => 'A',
|
||||
'row' => '1',
|
||||
'seat' => '1',
|
||||
'price' => 120000,
|
||||
],
|
||||
[
|
||||
'type' => 'VIP + LUNCH',
|
||||
'sector' => 'B',
|
||||
'row' => '2',
|
||||
'seat' => '3',
|
||||
'price' => 250000,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$response
|
||||
->assertOk()
|
||||
->assertJsonCount(2, 'data.variants')
|
||||
->assertJsonPath('data.variants.0.price', '120000.00')
|
||||
->assertJsonPath('data.variants.1.type', 'VIP + LUNCH');
|
||||
|
||||
$this->assertDatabaseCount('variantes', 2);
|
||||
$this->assertDatabaseHas('catalog_items', [
|
||||
'id' => $entry->id,
|
||||
'precio' => 120000,
|
||||
]);
|
||||
$this->assertDatabaseHas('inventories', [
|
||||
'id' => $response->json('data.variants.1.id') === null
|
||||
? 0
|
||||
: Variant::query()->findOrFail($response->json('data.variants.1.id'))->inventory_id,
|
||||
'real_stock' => 1,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_it_replaces_and_toggles_the_entry_image(): void
|
||||
{
|
||||
Storage::fake('s3');
|
||||
[$tenant, $entry] = $this->configuredEntry();
|
||||
$oldImage = Attachment::query()->create([
|
||||
'path' => 'catalog-items/old.png',
|
||||
'filename' => 'old.png',
|
||||
'type' => AttachmentType::Image,
|
||||
'mime_type' => 'image/png',
|
||||
'extension' => 'png',
|
||||
'size' => 10,
|
||||
]);
|
||||
Storage::disk('s3')->put($oldImage->path, 'old');
|
||||
$entry->allAttachments()->attach($oldImage->id, ['orden' => 0]);
|
||||
Sanctum::actingAs($this->createAdminAppUser($tenant));
|
||||
|
||||
$this->post('/api/v1/adminapp/tenant/desfile/entries/image', [
|
||||
'image' => UploadedFile::fake()->image('plano.png'),
|
||||
'is_enabled' => false,
|
||||
], ['Accept' => 'application/json'])
|
||||
->assertOk()
|
||||
->assertJsonPath('data.image.filename', 'plano.png')
|
||||
->assertJsonPath('data.image.is_enabled', false);
|
||||
|
||||
$this->assertDatabaseMissing('attachments', ['id' => $oldImage->id]);
|
||||
$this->assertCount(0, $entry->fresh()->attachments);
|
||||
$this->assertCount(1, $entry->fresh()->allAttachments);
|
||||
|
||||
$this->patchJson('/api/v1/adminapp/tenant/desfile/entries/image', [
|
||||
'is_enabled' => true,
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('data.image.is_enabled', true);
|
||||
|
||||
$this->assertCount(1, $entry->fresh()->attachments);
|
||||
}
|
||||
|
||||
public function test_it_rejects_duplicate_seats_and_cross_tenant_access(): void
|
||||
{
|
||||
[, $entry] = $this->configuredEntry();
|
||||
$foreignVariant = $this->createVariant($entry, 'NORMAL', 'A', '1', '1', 100);
|
||||
[$otherTenant] = $this->configuredEntry('other-desfile');
|
||||
Sanctum::actingAs($this->createAdminAppUser($otherTenant));
|
||||
|
||||
$this->putJson('/api/v1/adminapp/tenant/desfile/entries', [
|
||||
'variants' => [[
|
||||
'id' => $foreignVariant->id,
|
||||
...$this->variantPayload('NORMAL', 'A', '1', '1', 100),
|
||||
]],
|
||||
])
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors('variants.0.id');
|
||||
|
||||
$payload = [
|
||||
'variants' => [
|
||||
$this->variantPayload('NORMAL', 'A', '1', '1', 100),
|
||||
$this->variantPayload('normal', 'A', '1', '1', 200),
|
||||
],
|
||||
];
|
||||
|
||||
$this->putJson('/api/v1/adminapp/tenant/desfile/entries', $payload)
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors('variants.1');
|
||||
}
|
||||
|
||||
/** @return array{Tenant, CatalogItem} */
|
||||
private function configuredEntry(string $tenantCode = 'desfile_pura_tendencia'): array
|
||||
{
|
||||
$tenant = Tenant::query()->create([
|
||||
'codigo' => $tenantCode,
|
||||
'nombre' => 'Desfile',
|
||||
'dominio' => "{$tenantCode}.test",
|
||||
'website_type_code' => 'onticket',
|
||||
]);
|
||||
$menu = Menu::query()->firstOrCreate(
|
||||
['code' => 'adminapp.desfile.entradas'],
|
||||
['label' => 'Entradas', 'route' => '/admin/desfile/entradas'],
|
||||
);
|
||||
$tenant->menues()->attach($menu->code);
|
||||
$entry = CatalogItem::query()->create([
|
||||
'tenant_code' => $tenant->codigo,
|
||||
'slug' => 'entrada',
|
||||
'nombre' => 'Entrada',
|
||||
'precio' => 0,
|
||||
'inventory_policy' => InventoryPolicy::Tracked,
|
||||
'inventory_subject' => InventorySubject::Seat,
|
||||
'has_tickets' => true,
|
||||
]);
|
||||
|
||||
foreach ([
|
||||
'tipo' => ['VIP + LUNCH', 'NORMAL'],
|
||||
'sector' => ['A', 'B', 'C', 'D'],
|
||||
'fila' => ['1', '2'],
|
||||
'asiento' => ['1', '2', '3'],
|
||||
] as $code => $options) {
|
||||
$attribute = Attribute::query()->create([
|
||||
'tenant_codigo' => $tenant->codigo,
|
||||
'codigo' => $code,
|
||||
'nombre' => ucfirst($code),
|
||||
'type' => FieldType::Select,
|
||||
'is_required' => true,
|
||||
]);
|
||||
foreach ($options as $order => $option) {
|
||||
$attribute->options()->create([
|
||||
'value' => $option,
|
||||
'label' => $option,
|
||||
'sort_order' => $order + 1,
|
||||
]);
|
||||
}
|
||||
$entry->itemAttributes()->create([
|
||||
'attribute_id' => $attribute->id,
|
||||
'sort_order' => $entry->itemAttributes()->count() + 1,
|
||||
]);
|
||||
}
|
||||
|
||||
return [$tenant, $entry];
|
||||
}
|
||||
|
||||
private function createVariant(
|
||||
CatalogItem $entry,
|
||||
string $type,
|
||||
string $sector,
|
||||
string $row,
|
||||
string $seat,
|
||||
int $price,
|
||||
): Variant {
|
||||
$variant = $entry->variants()->create([
|
||||
'inventory_id' => Inventory::query()->create(['real_stock' => 1])->id,
|
||||
'precio' => $price,
|
||||
]);
|
||||
$itemAttributes = $entry->itemAttributes()->with('attribute')->get()->keyBy(
|
||||
fn (ItemAttribute $itemAttribute): string => $itemAttribute->attribute->codigo,
|
||||
);
|
||||
|
||||
foreach (compact('type', 'sector', 'row', 'seat') as $input => $value) {
|
||||
$code = ['type' => 'tipo', 'sector' => 'sector', 'row' => 'fila', 'seat' => 'asiento'][$input];
|
||||
$variant->definitions()->create([
|
||||
'item_attribute_id' => $itemAttributes[$code]->id,
|
||||
'value' => $value,
|
||||
]);
|
||||
}
|
||||
|
||||
return $variant;
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
private function variantPayload(
|
||||
string $type,
|
||||
string $sector,
|
||||
string $row,
|
||||
string $seat,
|
||||
int $price,
|
||||
): array {
|
||||
return compact('type', 'sector', 'row', 'seat', 'price');
|
||||
}
|
||||
|
||||
private function createAdminAppUser(Tenant $tenant): User
|
||||
{
|
||||
return User::factory()->create([
|
||||
'rol_codigo' => RoleCode::AdminApp->value,
|
||||
'tenant_codigo' => $tenant->codigo,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user