CluPilotCloud/tests/Feature/Billing/InvoiceListTest.php

85 lines
3.0 KiB
PHP

<?php
use App\Livewire\Admin\Invoices;
use App\Models\Customer;
use App\Models\Order;
use App\Models\User;
use App\Services\Billing\IssueInvoice;
use App\Support\CompanyProfile;
use Livewire\Livewire;
beforeEach(function () {
CompanyProfile::put([
'name' => 'CluPilot Cloud e.U.', 'address' => 'Dreherstraße 66/1/8',
'postcode' => '1110', 'city' => 'Wien', 'vat_id' => 'ATU00000000',
]);
});
function invoiceFor(string $customerName): App\Models\Invoice
{
$customer = Customer::factory()->create(['name' => $customerName]);
return app(IssueInvoice::class)->forOrders($customer, collect([
Order::factory()->create(['customer_id' => $customer->id, 'amount_cents' => 1900, 'currency' => 'EUR', 'status' => 'paid']),
]));
}
it('lists issued invoices with no way to edit or delete one', function () {
// The absence is the feature. A wrong invoice is corrected by cancelling it
// and issuing another, which is the only lawful way to correct one — so
// there is no button here whose absence needs explaining.
$invoice = invoiceFor('Muster GmbH');
Livewire::actingAs(operator('Owner'), 'operator')
->test(Invoices::class)
->assertSee($invoice->number)
->assertSee('Muster GmbH')
->assertDontSee(__('datacenters.edit'))
->assertDontSee(__('datacenters.delete'));
});
it('finds an invoice by its number and by the customer on it', function () {
invoiceFor('Muster GmbH');
$other = invoiceFor('Andere AG');
Livewire::actingAs(operator('Owner'), 'operator')
->test(Invoices::class)
->set('search', 'Andere')
->assertSee($other->number)
->assertDontSee('Muster GmbH');
});
it('serves the PDF from the frozen document', function () {
$invoice = invoiceFor('Muster GmbH');
$response = $this->actingAs(operator('Owner'), 'operator')
->get(route('admin.invoices.pdf', $invoice->uuid));
$response->assertOk()
->assertHeader('content-type', 'application/pdf');
expect(substr($response->streamedContent(), 0, 5))->toBe('%PDF-');
});
it('does not hand the PDF to an operator without the capability', function () {
$invoice = invoiceFor('Muster GmbH');
$this->actingAs(operator('Support'), 'operator')
->get(route('admin.invoices.pdf', $invoice->uuid))
->assertForbidden();
});
it('does not hand the PDF to a customer', function () {
// Its own test, and the guard named explicitly. Both matter: actingAs()
// calls Auth::shouldUse(), so a second actingAs() in the same test with no
// guard lands on whichever guard the FIRST one named — putting a User on
// the operator guard, where EnsureAdmin calls isActive() on it and the
// 403 becomes a 500. The trap is written up in CLAUDE.md and I walked into
// it anyway.
$invoice = invoiceFor('Muster GmbH');
$this->actingAs(User::factory()->create(), 'web')
->get(route('admin.invoices.pdf', $invoice->uuid))
->assertForbidden();
});