implemented event package addons with filament resource, event-admin purchase path and notifications, showing up in purchase history
This commit is contained in:
42
tests/Unit/Jobs/SyncPackageAddonToPaddleTest.php
Normal file
42
tests/Unit/Jobs/SyncPackageAddonToPaddleTest.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Jobs;
|
||||
|
||||
use App\Jobs\SyncPackageAddonToPaddle;
|
||||
use App\Models\PackageAddon;
|
||||
use App\Services\Paddle\PaddleAddonCatalogService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
|
||||
class SyncPackageAddonToPaddleTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_creates_and_updates_price_and_product(): void
|
||||
{
|
||||
$addon = PackageAddon::create([
|
||||
'key' => 'extra_photos_500',
|
||||
'label' => '+500 Fotos',
|
||||
'extra_photos' => 500,
|
||||
'metadata' => ['price_eur' => 5],
|
||||
]);
|
||||
|
||||
$service = Mockery::mock(PaddleAddonCatalogService::class);
|
||||
$service->shouldReceive('createProduct')
|
||||
->once()
|
||||
->andReturn(['id' => 'pro_addon_1']);
|
||||
$service->shouldReceive('createPrice')
|
||||
->once()
|
||||
->andReturn(['id' => 'pri_addon_1']);
|
||||
|
||||
$job = new SyncPackageAddonToPaddle($addon->id);
|
||||
$job->handle($service);
|
||||
|
||||
$addon->refresh();
|
||||
|
||||
$this->assertSame('pri_addon_1', $addon->price_id);
|
||||
$this->assertEquals('pro_addon_1', $addon->metadata['paddle_product_id']);
|
||||
$this->assertEquals('synced', $addon->metadata['paddle_sync_status']);
|
||||
}
|
||||
}
|
||||
43
tests/Unit/Services/EventAddonCatalogTest.php
Normal file
43
tests/Unit/Services/EventAddonCatalogTest.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services;
|
||||
|
||||
use App\Models\PackageAddon;
|
||||
use App\Services\Addons\EventAddonCatalog;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Tests\TestCase;
|
||||
|
||||
class EventAddonCatalogTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_prefers_database_addons_over_config(): void
|
||||
{
|
||||
Config::set('package-addons', [
|
||||
'extra_photos_small' => [
|
||||
'label' => 'Config Photos',
|
||||
'price_id' => 'pri_config',
|
||||
'increments' => ['extra_photos' => 100],
|
||||
],
|
||||
]);
|
||||
|
||||
PackageAddon::create([
|
||||
'key' => 'extra_photos_small',
|
||||
'label' => 'DB Photos',
|
||||
'price_id' => 'pri_db',
|
||||
'extra_photos' => 200,
|
||||
'active' => true,
|
||||
'sort' => 1,
|
||||
]);
|
||||
|
||||
$catalog = $this->app->make(EventAddonCatalog::class);
|
||||
|
||||
$addon = $catalog->find('extra_photos_small');
|
||||
|
||||
$this->assertNotNull($addon);
|
||||
$this->assertSame('DB Photos', $addon['label']);
|
||||
$this->assertSame('pri_db', $addon['price_id']);
|
||||
$this->assertSame(200, $addon['increments']['extra_photos']);
|
||||
}
|
||||
}
|
||||
@@ -165,4 +165,40 @@ class PackageLimitEvaluatorTest extends TestCase
|
||||
$this->assertTrue($summary['can_upload_photos']);
|
||||
$this->assertTrue($summary['can_add_guests']);
|
||||
}
|
||||
|
||||
public function test_assess_photo_upload_respects_extra_limits(): void
|
||||
{
|
||||
$package = Package::factory()->endcustomer()->create([
|
||||
'max_photos' => 5,
|
||||
]);
|
||||
|
||||
$tenant = Tenant::factory()->create();
|
||||
|
||||
$event = Event::factory()
|
||||
->for($tenant)
|
||||
->create();
|
||||
|
||||
$eventPackage = EventPackage::create([
|
||||
'event_id' => $event->id,
|
||||
'package_id' => $package->id,
|
||||
'purchased_price' => $package->price,
|
||||
'purchased_at' => now(),
|
||||
'used_photos' => 5,
|
||||
'used_guests' => 0,
|
||||
'gallery_expires_at' => now()->addDays(14),
|
||||
'extra_photos' => 5,
|
||||
])->fresh(['package']);
|
||||
|
||||
$violation = $this->evaluator->assessPhotoUpload($tenant->fresh(), $event->id);
|
||||
|
||||
$this->assertNull($violation, 'Upload should be allowed within extra photo allowance');
|
||||
|
||||
$eventPackage->update(['used_photos' => 10]);
|
||||
|
||||
$violation = $this->evaluator->assessPhotoUpload($tenant->fresh(), $event->id);
|
||||
|
||||
$this->assertNotNull($violation, 'Upload should be blocked after exceeding base + extras');
|
||||
$this->assertSame('photo_limit_exceeded', $violation['code']);
|
||||
$this->assertSame(0, $violation['meta']['remaining']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,4 +145,41 @@ class PackageUsageTrackerTest extends TestCase
|
||||
|
||||
EventFacade::assertDispatched(EventPackageGuestLimitReached::class);
|
||||
}
|
||||
|
||||
public function test_effective_limits_include_extras(): void
|
||||
{
|
||||
EventFacade::fake([
|
||||
EventPackagePhotoLimitReached::class,
|
||||
]);
|
||||
|
||||
$tenant = Tenant::factory()->create();
|
||||
$package = Package::factory()->endcustomer()->create([
|
||||
'max_photos' => 2,
|
||||
]);
|
||||
$event = Event::factory()->for($tenant)->create();
|
||||
|
||||
$eventPackage = EventPackage::create([
|
||||
'event_id' => $event->id,
|
||||
'package_id' => $package->id,
|
||||
'purchased_price' => $package->price,
|
||||
'purchased_at' => now(),
|
||||
'used_photos' => 2,
|
||||
'used_guests' => 0,
|
||||
'gallery_expires_at' => now()->addDays(7),
|
||||
'extra_photos' => 2,
|
||||
])->fresh(['package']);
|
||||
|
||||
/** @var PackageUsageTracker $tracker */
|
||||
$tracker = app(PackageUsageTracker::class);
|
||||
|
||||
// Base limit reached but extras still available; no limit event expected yet.
|
||||
$tracker->recordPhotoUsage($eventPackage, 1, 1);
|
||||
EventFacade::assertNotDispatched(EventPackagePhotoLimitReached::class);
|
||||
|
||||
// Now consume extras and hit the effective limit.
|
||||
$eventPackage->used_photos = 4;
|
||||
$tracker->recordPhotoUsage($eventPackage, 3, 1);
|
||||
|
||||
EventFacade::assertDispatched(EventPackagePhotoLimitReached::class);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user