85 lines
3.0 KiB
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();
|
|
});
|