CluPilotCloud/tests/Feature/Admin/GrantPlanTest.php

142 lines
5.4 KiB
PHP

<?php
use App\Livewire\Admin\Customers;
use App\Livewire\Admin\GrantPlan;
use App\Models\Customer;
use App\Models\PlanFamily;
use App\Models\Subscription;
use App\Models\SubscriptionAddon;
use App\Provisioning\Jobs\AdvanceRunJob;
use App\Services\Billing\PlanCatalogue;
use Illuminate\Support\Facades\Queue;
use Livewire\Livewire;
/**
* The console's side of granting: a modal (R20) on the customer's own row,
* gated by its own capability rather than merely hidden (mutation target #3).
*
* 'fsn' is seeded by the datacenters migration itself (baseline data, like
* the mailbox seeding) — no factory needed for it here.
*/
it('refuses to open for an operator without the capability, not merely hides the button', function () {
$customer = Customer::factory()->create();
// Support has customers.manage (can suspend, impersonate) but never had
// customers.grant_plan — giving away service is a bigger deal than a
// reversible suspend, and the seed migration deliberately keeps this one
// to Owner. A capability check that only hid the button would still let
// this request through.
Livewire::actingAs(operator('Support'), 'operator')
->test(GrantPlan::class, ['uuid' => $customer->uuid])
->assertForbidden();
});
it('hides the grant button from an operator without the capability', function () {
Customer::factory()->create();
Livewire::actingAs(operator('Support'), 'operator')
->test(Customers::class)
->assertDontSee(__('admin.grant_action'));
});
it('shows the grant button to the Owner', function () {
Customer::factory()->create();
Livewire::actingAs(operator('Owner'), 'operator')
->test(Customers::class)
->assertSee(__('admin.grant_action'));
});
it('grants a free package to a customer with no contract yet', function () {
Queue::fake();
$customer = Customer::factory()->create();
$owner = operator('Owner');
Livewire::actingAs($owner, 'operator')
->test(GrantPlan::class, ['uuid' => $customer->uuid])
->set('plan', 'team')
->set('term', 'monthly')
->set('datacenter', 'fsn')
->set('priceEuros', '0')
->set('note', 'Testkunde')
->call('grant')
->assertHasNoErrors();
$subscription = Subscription::query()->where('customer_id', $customer->id)->sole();
expect($subscription->price_cents)->toBe(0)
->and($subscription->isGranted())->toBeTrue()
->and($subscription->granted_by)->toBe($owner->id);
Queue::assertPushed(AdvanceRunJob::class);
});
it('still lets an internal package be granted — that is the one door it has', function () {
// Ein internes Paket verlässt sellable() und damit Preisblatt, Bestellung
// und Warenkorb (Aufgabe 6). Dieses Formular liest bewusst grantable()
// statt sellable() für sein Dropdown UND seine Validierung — sonst wäre
// das interne Paket auch hier draußen und nirgends mehr vergebbar.
$family = PlanFamily::create([
'key' => 'test', 'name' => 'Test', 'tier' => 0,
'sales_enabled' => true, 'internal' => true,
]);
$version = app(PlanCatalogue::class)->draft($family, [
'quota_gb' => 5, 'traffic_gb' => 1000, 'seats' => 3, 'free_months' => 0,
'ram_mb' => 4096, 'cores' => 2, 'disk_gb' => 20, 'performance' => 'standard',
'template_vmid' => 9000, 'features' => [],
], ['monthly' => 100, 'yearly' => 1200]);
app(PlanCatalogue::class)->publish($version);
$customer = Customer::factory()->create();
Livewire::actingAs(operator('Owner'), 'operator')
->test(GrantPlan::class, ['uuid' => $customer->uuid])
->set('plan', 'test')
->set('term', 'monthly')
->set('datacenter', 'fsn')
->set('priceEuros', '0')
->call('grant')
->assertHasNoErrors();
expect(Subscription::query()->where('customer_id', $customer->id)->sole()->plan)->toBe('test');
});
it('grants a discounted module onto an existing contract', function () {
$customer = Customer::factory()->create();
Subscription::factory()->create(['customer_id' => $customer->id, 'status' => 'active']);
$owner = operator('Owner');
Livewire::actingAs($owner, 'operator')
->test(GrantPlan::class, ['uuid' => $customer->uuid])
->assertSet('kind', 'addon') // a contract already exists — default to the module form
->set('addonKey', 'priority_support')
->set('quantity', 1)
->set('priceEuros', '5,00')
->call('grant')
->assertHasNoErrors();
$addon = SubscriptionAddon::query()->where('addon_key', 'priority_support')->sole();
expect($addon->price_cents)->toBe(500)
->and($addon->isGranted())->toBeTrue()
->and($addon->granted_by)->toBe($owner->id);
});
it('shows who granted what, when, and until when', function () {
$customer = Customer::factory()->create();
$owner = operator('Owner');
$until = now()->addMonths(3);
Subscription::factory()->create([
'customer_id' => $customer->id, 'status' => 'active',
'price_cents' => 0, 'granted_by' => $owner->id, 'granted_at' => now(),
'grant_note' => 'Sponsoring', 'granted_until' => $until, 'catalogue_price_cents' => 17900,
]);
Livewire::actingAs($owner, 'operator')
->test(GrantPlan::class, ['uuid' => $customer->uuid])
->assertSee($owner->name)
->assertSee('Sponsoring')
->assertSee($until->local()->isoFormat('D. MMM YYYY'));
});