CluPilotCloud/tests/Feature/Admin/CustomerDetailTabsTest.php

169 lines
6.3 KiB
PHP

<?php
use App\Livewire\Admin\CustomerDetail;
use App\Models\Customer;
use App\Models\Instance;
use App\Models\Invoice;
use App\Models\InvoiceSeries;
use App\Models\Order;
use App\Models\Subscription;
use App\Support\CompanyProfile;
use Livewire\Livewire;
/**
* The customer page shows the customer.
*
* The first version was a compose box with three lists under it, and the
* complaint was exactly right: it showed nothing ABOUT the customer. An operator
* on the phone needs the address, the package, whether the last invoice was paid
* and what was already said — and none of that was on the page.
*/
beforeEach(function () {
CompanyProfile::put([
'name' => 'CluPilot GmbH', 'address' => 'Musterstraße 1',
'postcode' => '1010', 'city' => 'Wien', 'vat_id' => 'ATU12345678',
]);
$this->customer = Customer::factory()->create([
'name' => 'Beispiel GmbH',
'contact_name' => 'Bea Berger',
'phone' => '+43 1 2345678',
'billing_address' => "Musterstraße 2\n1020 Wien",
'vat_id' => 'ATU99999999',
'customer_type' => Customer::TYPE_BUSINESS,
]);
});
it('opens on the details', function () {
Livewire::actingAs(operator('Owner'), 'operator')
->test(CustomerDetail::class, ['uuid' => $this->customer->uuid])
->assertSet('tab', 'overview')
// The things somebody on the phone needs first.
->assertSee('Bea Berger')
->assertSee('+43 1 2345678')
->assertSee('ATU99999999')
->assertSee('1020 Wien')
->assertSee(__('customer_detail.type_business'));
});
it('remembers the tab across a reload, because it is in the URL', function () {
Livewire::withQueryParams(['tab' => 'billing'])
->actingAs(operator('Owner'), 'operator')
->test(CustomerDetail::class, ['uuid' => $this->customer->uuid])
->assertSet('tab', 'billing')
->assertSee(__('customer_detail.invoices'));
});
it('falls back to the first tab when the URL names one that does not exist', function () {
Livewire::withQueryParams(['tab' => 'wat'])
->actingAs(operator('Owner'), 'operator')
->test(CustomerDetail::class, ['uuid' => $this->customer->uuid])
->assertSet('tab', 'overview');
});
it('says a customer nobody asked is treated as a consumer', function () {
// Not cosmetic: it decides whether a withdrawal right exists at all, and the
// page must not let "nobody asked" read as "business".
$unknown = Customer::factory()->create(['customer_type' => null]);
Livewire::actingAs(operator('Owner'), 'operator')
->test(CustomerDetail::class, ['uuid' => $unknown->uuid])
->assertSee(__('customer_detail.type_unknown'));
});
it('shows the contract the customer is owed, with its own frozen figures', function () {
// The contract's own price, not today's catalogue: a price rise does not
// reach back into what somebody already bought.
Subscription::factory()->plan('team')->create([
'customer_id' => $this->customer->id,
'status' => 'active',
]);
Livewire::withQueryParams(['tab' => 'package'])
->actingAs(operator('Owner'), 'operator')
->test(CustomerDetail::class, ['uuid' => $this->customer->uuid])
->assertSee(__('billing.plan.team'))
->assertSee('500 GB') // team's quota
->assertSee(__('customer_detail.price'));
});
it('shows the machine, and says so plainly when there is none', function () {
Livewire::withQueryParams(['tab' => 'package'])
->actingAs(operator('Owner'), 'operator')
->test(CustomerDetail::class, ['uuid' => $this->customer->uuid])
->assertSee(__('customer_detail.no_contract'));
Subscription::factory()->plan('start')->create(['customer_id' => $this->customer->id, 'status' => 'active']);
Instance::factory()->create([
'customer_id' => $this->customer->id,
'subdomain' => 'kanzlei-berger',
'status' => 'active',
]);
Livewire::withQueryParams(['tab' => 'package'])
->actingAs(operator('Owner'), 'operator')
->test(CustomerDetail::class, ['uuid' => $this->customer->uuid])
->assertSee('kanzlei-berger');
});
it('shows what was invoiced and what was bought, which are two questions', function () {
// An order is a purchase; an invoice is a document about one. A customer
// asking "was das schon verrechnet?" is asking about the second.
InvoiceSeries::query()->firstOrCreate(
['kind' => 'invoice'],
['name' => 'Rechnungen', 'prefix' => 'RE', 'active' => true],
);
Order::factory()->create([
'customer_id' => $this->customer->id,
'plan' => 'start',
'amount_cents' => 4900,
'status' => 'paid',
]);
Invoice::create([
'invoice_series_id' => InvoiceSeries::query()->first()->id,
'customer_id' => $this->customer->id,
'number' => 'RE-2026-0007',
'number_year' => 2026,
'number_sequence' => 7,
'issued_on' => now()->toDateString(),
'due_on' => now()->addDays(14)->toDateString(),
'snapshot' => ['meta' => ['number' => 'RE-2026-0007']],
'net_cents' => 4900,
'tax_cents' => 980,
'gross_cents' => 5880,
'currency' => 'EUR',
]);
Livewire::withQueryParams(['tab' => 'billing'])
->actingAs(operator('Owner'), 'operator')
->test(CustomerDetail::class, ['uuid' => $this->customer->uuid])
->assertSee('RE-2026-0007')
->assertSee('58,80')
->assertSee(__('customer_detail.orders'));
});
it('offers writing last, because it is what you do after reading', function () {
$tabs = CustomerDetail::TABS;
expect($tabs[0])->toBe('overview')
->and(end($tabs))->toBe('compose');
// Every tab has a label, or the bar reads "customer_detail.tab.x".
foreach ($tabs as $tab) {
expect(__('customer_detail.tab.'.$tab))->not->toBe('customer_detail.tab.'.$tab);
}
});
it('does not print a lang key at anybody', function () {
// The badge read "customers.status.active" once. A key with no file behind
// it renders as itself and Laravel says nothing about it.
$page = Livewire::actingAs(operator('Owner'), 'operator')
->test(CustomerDetail::class, ['uuid' => $this->customer->uuid]);
$page->assertDontSee('customers.status.')
->assertDontSee('customer_detail.tab.')
->assertSee(__('admin.status.active'));
});