179 lines
8.7 KiB
PHP
179 lines
8.7 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Ticket;
|
|
|
|
use App\Domains\Core\Auth\Models\User;
|
|
use App\Domains\Ticketing\Ticket\Services\AdminAppTicketExcelService;
|
|
use App\Domains\Ticketing\Ticket\Services\AdminAppTicketPdfService;
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Laravel\Sanctum\Sanctum;
|
|
use Mockery\MockInterface;
|
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
|
use Tests\TestCase;
|
|
|
|
class AdminAppTicketEventScopeTest extends TestCase
|
|
{
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
// Keep HTTP tests isolated from legacy migrations incompatible with SQLite.
|
|
Schema::create('tenants', function (Blueprint $table): void {
|
|
$table->id();
|
|
$table->string('codigo');
|
|
$table->string('timezone')->default('UTC');
|
|
$table->boolean('allow_ticket_refund')->default(false);
|
|
$table->boolean('allow_ticket_total_refund')->default(false);
|
|
$table->boolean('allow_ticket_partial_refund')->default(false);
|
|
});
|
|
Schema::create('menues', function (Blueprint $table): void {
|
|
$table->id();
|
|
$table->string('code');
|
|
});
|
|
Schema::create('tenants_menues', function (Blueprint $table): void {
|
|
$table->string('tenant_code');
|
|
$table->string('menu_code');
|
|
});
|
|
Schema::create('users', function (Blueprint $table): void {
|
|
$table->id();
|
|
$table->string('nombre_apellido');
|
|
$table->softDeletes();
|
|
});
|
|
Schema::create('tickets', function (Blueprint $table): void {
|
|
$table->id();
|
|
$table->string('tenant_code');
|
|
$table->unsignedBigInteger('event_id')->nullable();
|
|
$table->string('ticket');
|
|
foreach (['source_variant_id', 'source_catalog_item_id', 'source_purchase_item_id', 'scanner_user_id', 'user_id'] as $column) {
|
|
$table->unsignedBigInteger($column)->nullable();
|
|
}
|
|
foreach (['used_at', 'disabled_at', 'cancelled_at', 'refunded_at'] as $column) {
|
|
$table->timestamp($column)->nullable();
|
|
}
|
|
$table->timestamps();
|
|
});
|
|
Schema::create('compras', function (Blueprint $table): void {
|
|
$table->id();
|
|
$table->string('tenant_codigo');
|
|
$table->unsignedBigInteger('event_id')->nullable();
|
|
$table->string('nombre_apellido');
|
|
});
|
|
Schema::create('compra_items', function (Blueprint $table): void {
|
|
$table->id();
|
|
$table->unsignedBigInteger('compra_id');
|
|
$table->decimal('precio_unitario', 12, 2);
|
|
});
|
|
Schema::create('ticket_refunds', function (Blueprint $table): void {
|
|
$table->id();
|
|
$table->unsignedBigInteger('ticket_id')->nullable();
|
|
$table->unsignedBigInteger('purchase_item_id');
|
|
$table->decimal('amount', 12, 2);
|
|
});
|
|
Schema::create('desfile_entry_reservations', function (Blueprint $table): void {
|
|
$table->id();
|
|
$table->unsignedBigInteger('ticket_id');
|
|
$table->softDeletes();
|
|
});
|
|
Schema::create('value_changes', function (Blueprint $table): void {
|
|
$table->id();
|
|
$table->string('tenant_code');
|
|
$table->string('trackable_type');
|
|
$table->unsignedBigInteger('trackable_id');
|
|
$table->string('attribute');
|
|
$table->string('old_value')->nullable();
|
|
$table->string('new_value')->nullable();
|
|
$table->timestamp('changed_at');
|
|
$table->string('actor_type');
|
|
$table->unsignedBigInteger('user_id')->nullable();
|
|
});
|
|
|
|
DB::table('tenants')->insert(['codigo' => 'onticket']);
|
|
DB::table('menues')->insert(['code' => 'onticket.adminapp.tickets']);
|
|
DB::table('tenants_menues')->insert(['tenant_code' => 'onticket', 'menu_code' => 'onticket.adminapp.tickets']);
|
|
foreach ([1 => ['onticket', 10], 2 => ['onticket', 20], 3 => ['onticket', null], 4 => ['other', 10]] as $id => [$tenant, $event]) {
|
|
DB::table('tickets')->insert([
|
|
'id' => $id, 'tenant_code' => $tenant, 'event_id' => $event,
|
|
'ticket' => "ticket-{$id}", 'used_at' => now(),
|
|
]);
|
|
DB::table('compras')->insert(['id' => $id, 'tenant_codigo' => $tenant, 'event_id' => $event, 'nombre_apellido' => 'Cliente']);
|
|
DB::table('compra_items')->insert(['id' => $id, 'compra_id' => $id, 'precio_unitario' => 100]);
|
|
DB::table('ticket_refunds')->insert(['purchase_item_id' => $id, 'amount' => $id * 10]);
|
|
}
|
|
$this->actingAsAdmin(10);
|
|
}
|
|
|
|
public function test_list_counts_refunded_total_and_search_cannot_escape_the_user_event(): void
|
|
{
|
|
$this->getJson('/api/v1/adminapp/tenant/tickets?sort_by=id&event_id=20')
|
|
->assertOk()->assertJsonCount(1, 'data')->assertJsonPath('data.0.id', 1)
|
|
->assertJsonPath('scanned_tickets', 1)->assertJsonPath('total_tickets', 1)
|
|
->assertJsonPath('refunded_total', '10.00');
|
|
$this->getJson('/api/v1/adminapp/tenant/tickets?q=2')->assertOk()->assertJsonCount(0, 'data');
|
|
// Status uses sorting in memory rather than the database.
|
|
$this->getJson('/api/v1/adminapp/tenant/tickets?sort_by=status')
|
|
->assertOk()->assertJsonCount(1, 'data')->assertJsonPath('data.0.id', 1);
|
|
DB::table('tickets')->where('id', 1)->update(['used_at' => null]);
|
|
$this->getJson('/api/v1/adminapp/tenant/tickets?status=active')
|
|
->assertOk()->assertJsonCount(1, 'data')
|
|
->assertJsonPath('scanned_tickets', 0)->assertJsonPath('total_tickets', 1);
|
|
}
|
|
|
|
public function test_foreign_unassigned_and_other_tenant_tickets_cannot_be_modified_or_refunded(): void
|
|
{
|
|
foreach ([2, 3, 4] as $id) {
|
|
$this->postJson("/api/v1/adminapp/tenant/tickets/{$id}/cancel", ['event_id' => 20])->assertNotFound();
|
|
$this->getJson("/api/v1/adminapp/tenant/tickets/{$id}/refund")->assertNotFound();
|
|
$this->postJson("/api/v1/adminapp/tenant/tickets/{$id}/refund", ['refund_type' => 'total', 'event_id' => 20])->assertNotFound();
|
|
$this->assertDatabaseHas('tickets', ['id' => $id, 'cancelled_at' => null, 'refunded_at' => null]);
|
|
}
|
|
$this->assertDatabaseCount('value_changes', 0);
|
|
$this->assertDatabaseCount('ticket_refunds', 4);
|
|
}
|
|
|
|
public function test_own_ticket_can_be_cancelled_and_refund_requests_reach_business_validation(): void
|
|
{
|
|
DB::table('tickets')->where('id', 1)->update(['used_at' => null]);
|
|
$this->getJson('/api/v1/adminapp/tenant/tickets/1/refund')->assertUnprocessable();
|
|
$this->postJson('/api/v1/adminapp/tenant/tickets/1/refund', ['refund_type' => 'total'])->assertUnprocessable();
|
|
$this->postJson('/api/v1/adminapp/tenant/tickets/1/cancel')->assertOk();
|
|
$this->assertNotNull(DB::table('tickets')->where('id', 1)->value('cancelled_at'));
|
|
}
|
|
|
|
public function test_unscoped_admin_keeps_the_tenant_scope(): void
|
|
{
|
|
$this->actingAsAdmin(null);
|
|
$this->getJson('/api/v1/adminapp/tenant/tickets?sort_by=id')
|
|
->assertOk()->assertJsonCount(3, 'data')->assertJsonPath('total_tickets', 3)
|
|
->assertJsonPath('refunded_total', '60.00');
|
|
DB::table('tickets')->where('id', 3)->update(['used_at' => null]);
|
|
$this->postJson('/api/v1/adminapp/tenant/tickets/3/cancel')->assertOk();
|
|
$this->postJson('/api/v1/adminapp/tenant/tickets/4/cancel')->assertNotFound();
|
|
}
|
|
|
|
public function test_pdf_and_excel_receive_only_the_tickets_of_the_user_event(): void
|
|
{
|
|
foreach ([AdminAppTicketPdfService::class, AdminAppTicketExcelService::class] as $class) {
|
|
$this->mock($class, function (MockInterface $mock) use ($class): void {
|
|
$mock->shouldReceive('download')->once()->withArgs(
|
|
fn ($tenant, Collection $tickets, $timezone): bool => $tenant->codigo === 'onticket'
|
|
&& $tickets->pluck('id')->all() === [1] && $timezone === 'UTC'
|
|
)->andReturn($class === AdminAppTicketPdfService::class
|
|
? response('pdf') : new StreamedResponse(fn () => print ('excel')));
|
|
});
|
|
}
|
|
foreach (['pdf', 'excel'] as $format) {
|
|
$this->getJson("/api/v1/adminapp/tenant/tickets/{$format}?timezone=UTC&event_id=20")->assertOk();
|
|
}
|
|
}
|
|
|
|
private function actingAsAdmin(?int $eventId): void
|
|
{
|
|
$user = new User;
|
|
$user->setRawAttributes(['id' => 1, 'rol_codigo' => 'adminapp', 'tenant_codigo' => 'onticket', 'event_id' => $eventId]);
|
|
Sanctum::actingAs($user);
|
|
}
|
|
}
|