Files
WMS/tests/Feature/Platform/PlatformPriceCrudTest.php
2026-09-21 10:14:57 +01:00

318 lines
11 KiB
PHP
Executable File

<?php
namespace Tests\Feature\Platform;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Modules\Platform\app\Models\PlatformPrice;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class PlatformPriceCrudTest extends TestCase
{
use RefreshDatabase;
/**
* Ensure the customer-facing Platform pricing page only shows active prices in display order.
*/
public function test_platform_pricing_page_displays_active_prices_in_card_grid(): void
{
$user = User::factory()->create();
PlatformPrice::query()->delete();
PlatformPrice::create($this->pricePayload(['code' => 'pro', 'name' => 'Pro', 'sort_order' => 30]));
PlatformPrice::create($this->pricePayload(['code' => 'basic', 'name' => 'Basic', 'sort_order' => 10]));
PlatformPrice::create($this->pricePayload(['code' => 'legacy', 'name' => 'Legacy', 'is_active' => false, 'sort_order' => 1]));
$response = $this->actingAs($user)->get(route('platform.pricing.index'));
$response->assertOk()
->assertSee('Pricing', false)
->assertSee('Basic', false)
->assertSee('Pro', false)
->assertDontSee('Legacy', false)
->assertSeeInOrder(['Basic', 'Pro']);
}
/**
* Ensure the admin pricing index remains an Administration CRUD area.
*/
public function test_platform_admin_can_view_price_crud_index(): void
{
$admin = User::factory()->mpbaPlatformAdmin()->create();
PlatformPrice::query()->delete();
PlatformPrice::create($this->pricePayload(['code' => 'advanced', 'name' => 'Advanced']));
$this->actingAs($admin)
->get(route('platform.prices.index'))
->assertOk()
->assertSee('Pricing Administration', false)
->assertSee('Advanced', false)
->assertSee('Add price', false)
->assertSee(route('platform.prices.create'), false);
}
/**
* Ensure only MPBA platform admins can reach price administration.
*/
public function test_non_platform_admin_cannot_access_price_crud(): void
{
$user = User::factory()->create();
$this->actingAs($user)
->get(route('platform.prices.index'))
->assertForbidden();
}
/**
* Ensure create renders with default form values.
*/
public function test_platform_admin_can_open_create_price_form(): void
{
$admin = User::factory()->mpbaPlatformAdmin()->create();
$this->actingAs($admin)
->get(route('platform.prices.create'))
->assertOk()
->assertSee('Create Price', false)
->assertSee('Currency', false);
}
/**
* Ensure store normalises amounts, code, booleans, currency and feature rows.
*/
public function test_platform_admin_can_create_price(): void
{
$admin = User::factory()->mpbaPlatformAdmin()->create();
$response = $this->actingAs($admin)->withSession(['_token' => 'test-token'])->post(route('platform.prices.store'), $this->csrfPayload([
'code' => '',
'name' => 'Ultimate Plan',
'strapline' => 'Full control',
'description' => 'Unlimited warehouses and users.',
'monthly_amount' => '199.99',
'annual_amount' => '1999.00',
'currency' => 'GBP',
'badge_label' => 'Best Value',
'features' => "Unlimited warehouses\nUnlimited users\nPriority support\n",
'sort_order' => 40,
'is_featured' => '1',
'is_active' => '1',
]));
$response->assertRedirect(route('platform.prices.index'));
$this->assertDatabaseHas('platform_prices', [
'code' => 'ultimate-plan',
'name' => 'Ultimate Plan',
'monthly_amount' => 19999,
'annual_amount' => 199900,
'currency' => 'gbp',
'is_featured' => true,
'is_active' => true,
]);
$this->assertSame(
['Unlimited warehouses', 'Unlimited users', 'Priority support'],
PlatformPrice::where('code', 'ultimate-plan')->firstOrFail()->featureRows(),
);
}
/**
* Ensure validation protects required fields and amount limits.
*/
public function test_create_price_validates_required_fields(): void
{
$admin = User::factory()->mpbaPlatformAdmin()->create();
$this->actingAs($admin)
->from(route('platform.prices.create'))
->withSession(['_token' => 'test-token'])
->post(route('platform.prices.store'), $this->csrfPayload([
'name' => '',
'monthly_amount' => '-1',
'currency' => 'GB',
'sort_order' => -5,
]))
->assertRedirect(route('platform.prices.create'))
->assertSessionHasErrors(['name', 'monthly_amount', 'currency', 'sort_order']);
}
/**
* Ensure edit renders the current price.
*/
public function test_platform_admin_can_open_edit_price_form(): void
{
$admin = User::factory()->mpbaPlatformAdmin()->create();
PlatformPrice::query()->delete();
$price = PlatformPrice::create($this->pricePayload(['code' => 'basic-edit', 'name' => 'Basic']));
$this->actingAs($admin)
->get(route('platform.prices.edit', $price))
->assertOk()
->assertSee('Edit Basic', false)
->assertSee('Basic', false);
}
/**
* Ensure update preserves unique-code validation for the current record.
*/
public function test_platform_admin_can_update_price(): void
{
$admin = User::factory()->mpbaPlatformAdmin()->create();
PlatformPrice::query()->delete();
$price = PlatformPrice::create($this->pricePayload(['code' => 'basic', 'name' => 'Basic']));
$this->actingAs($admin)->withSession(['_token' => 'test-token'])->put(route('platform.prices.update', $price), $this->csrfPayload([
'code' => 'basic',
'name' => 'Basic Updated',
'strapline' => 'Entry level stock control',
'description' => 'Updated description.',
'monthly_amount' => '29.00',
'annual_amount' => '',
'currency' => 'GBP',
'badge_label' => '',
'features' => "2 warehouses\n10 users",
'sort_order' => 5,
'is_active' => '1',
]))->assertRedirect(route('platform.prices.index'));
$this->assertDatabaseHas('platform_prices', [
'id' => $price->id,
'code' => 'basic',
'name' => 'Basic Updated',
'monthly_amount' => 2900,
'annual_amount' => null,
'sort_order' => 5,
'is_featured' => false,
'is_active' => true,
]);
}
/**
* Ensure duplicate codes are rejected on update.
*/
public function test_price_code_must_remain_unique(): void
{
$admin = User::factory()->mpbaPlatformAdmin()->create();
PlatformPrice::query()->delete();
PlatformPrice::create($this->pricePayload(['code' => 'basic', 'name' => 'Basic']));
$price = PlatformPrice::create($this->pricePayload(['code' => 'advanced', 'name' => 'Advanced']));
$this->actingAs($admin)
->from(route('platform.prices.edit', $price))
->withSession(['_token' => 'test-token'])
->put(route('platform.prices.update', $price), $this->csrfPayload([
...$this->requestPayload(),
'code' => 'basic',
'name' => 'Advanced',
]))
->assertRedirect(route('platform.prices.edit', $price))
->assertSessionHasErrors(['code']);
}
/**
* Ensure destroy removes the price.
*/
public function test_platform_admin_can_delete_price(): void
{
$admin = User::factory()->mpbaPlatformAdmin()->create();
PlatformPrice::query()->delete();
$price = PlatformPrice::create($this->pricePayload(['code' => 'legacy', 'name' => 'Legacy']));
$this->actingAs($admin)
->withSession(['_token' => 'test-token'])
->delete(route('platform.prices.destroy', $price), ['_token' => 'test-token'])
->assertRedirect(route('platform.prices.index'));
$this->assertDatabaseMissing('platform_prices', ['id' => $price->id]);
}
/**
* Ensure currencies render with correct symbols.
*/
#[DataProvider('currencySymbolProvider')]
public function test_price_currency_symbol_and_formatting(string $currency, string $expected): void
{
$price = new PlatformPrice(['currency' => $currency]);
$this->assertSame($expected, $price->currencySymbol());
$this->assertStringStartsWith($expected, $price->formatAmount(12345));
$this->assertSame('—', $price->formatAmount(null));
}
/**
* @return array<int, array{0: string, 1: string}>
*/
public static function currencySymbolProvider(): array
{
return [
['gbp', '£'],
['eur', '€'],
['usd', '$'],
['aud', 'AUD '],
];
}
/**
* @param array<string, mixed> $overrides
*
* @return array<string, mixed>
*/
private function pricePayload(array $overrides = []): array
{
return [
'code' => 'basic',
'name' => 'Basic',
'strapline' => 'Starter WMS platform access.',
'description' => 'Core warehouse management tools.',
'monthly_amount' => 4900,
'annual_amount' => 49000,
'currency' => 'gbp',
'badge_label' => 'Starter',
'features' => ['2 warehouses', '10 users', 'Standard support'],
'sort_order' => 10,
'is_featured' => false,
'is_active' => true,
...$overrides,
];
}
/**
* @return array<string, mixed>
*/
private function requestPayload(): array
{
return [
'code' => 'advanced',
'name' => 'Advanced',
'strapline' => 'Expanded controls.',
'description' => 'Larger warehouse estate support.',
'monthly_amount' => '99.00',
'annual_amount' => '999.00',
'currency' => 'GBP',
'badge_label' => 'Popular',
'features' => "5 warehouses\n100 users",
'sort_order' => 20,
'is_featured' => '1',
'is_active' => '1',
];
}
/**
* Return payload with a valid test CSRF token.
*
* @param array<string, mixed> $payload
*
* @return array<string, mixed>
*/
private function csrfPayload(array $payload = []): array
{
return ['_token' => 'test-token'] + $payload;
}
/**
* Seed the test session with the matching CSRF token.
*/
}