Files
compras/tests/Feature/ProcurementReportsTest.php
T
nala 48347e2d3a
tests / ci (push) Canceled after 0s
commit5
2026-10-30 10:55:18 +02:00

122 lines
7.1 KiB
PHP

<?php
use App\Enums\PurchasingRole;
use App\Models\AuditLog;
use App\Models\Department;
use App\Models\Requisition;
use App\Models\User;
use App\Services\ExternalPaymentWorkflow;
use Illuminate\Support\Facades\DB;
use Inertia\Testing\AssertableInertia as Assert;
require_once __DIR__.'/../Support/ReceiptFixture.php';
require_once __DIR__.'/../Support/PaymentFixture.php';
beforeEach(function () {
$this->f = receiptFixture();
$this->actingAs($this->f['issuer']);
});
function reportCopy(Requisition $original, string $number, array $attributes = []): Requisition
{
$copy = $original->replicate();
$copy->forceFill(['number' => $number, ...$attributes])->save();
return $copy;
}
test('report entry connects visible requests to the existing process and report without business writes', function () {
$audits = AuditLog::query()->count();
$this->get('/relatorios')->assertOk()->assertHeader('Cache-Control', 'max-age=0, no-store, private')->assertInertia(fn (Assert $page) => $page
->component('reports/procurement')->has('records.data', 1)
->where('records.data.0.id', $this->f['r']->id)
->where('records.data.0.total_minor', (string) $this->f['r']->total_minor)
->where('records.data.0.order_number', $this->f['order']->number)
->where('counts.approved', 1));
$this->get('/requisicoes/'.$this->f['r']->id.'/processo')->assertOk();
$this->get('/requisicoes/'.$this->f['r']->id.'/relatorio')->assertOk();
expect(AuditLog::query()->count())->toBe($audits);
});
test('report dates include the whole final day and allow an end date without a start', function () {
$this->f['r']->forceFill(['created_at' => '2026-01-02 23:59:59'])->save();
reportCopy($this->f['r'], 'NEXT-DAY', ['created_at' => '2026-01-03 00:00:00', 'status' => 'draft']);
$this->get('/relatorios?from=2026-01-02&to=2026-01-02&status=approved')->assertInertia(fn (Assert $page) => $page->where('records.total', 1)->where('counts.approved', 1)->where('filters.to', '2026-01-02'));
$this->get('/relatorios?to=2026-01-02')->assertInertia(fn (Assert $page) => $page->where('records.total', 1));
$csv = $this->get('/relatorios/compras.csv?from=2026-01-02&to=2026-01-02&status=approved')->assertOk()->streamedContent();
expect($csv)->toContain($this->f['r']->number)->not->toContain('NEXT-DAY')->toContain('2026-01-02');
});
test('report counts and CSV use all filtered requests rather than the displayed page', function () {
foreach (range(1, 206) as $index) {
reportCopy($this->f['r'], 'REPORT-'.$index);
}
$this->get('/relatorios')->assertInertia(fn (Assert $page) => $page->has('records.data', 25)->where('records.total', 207)->where('counts.approved', 207));
$csv = $this->get('/relatorios/compras.csv?page=2')->assertOk()->streamedContent();
expect($csv)->toContain($this->f['r']->number)->toContain('REPORT-206');
$this->get('/relatorios?search=REPORT-2')->assertInertia(fn (Assert $page) => $page->where('records.total', 18));
});
test('report filters and CSV cannot expose another department or company', function () {
$own = $this->f;
$other = receiptFixture();
$other['r']->forceFill(['number' => 'OTHER-COMPANY-SECRET'])->save();
$this->actingAs($own['issuer']);
$department = Department::query()->create(['tenant_id' => $own['tenant']->id, 'name' => 'Other department']);
reportCopy($own['r'], 'OTHER-DEPARTMENT-SECRET', ['department_id' => $department->id]);
$head = User::factory()->create(['tenant_id' => $own['tenant']->id, 'department_id' => $own['r']->department_id, 'purchasing_role' => PurchasingRole::DepartmentHead]);
$this->actingAs($head)->get('/relatorios')->assertInertia(fn (Assert $page) => $page->where('records.total', 1)->has('departments', 1));
$this->get('/relatorios?department_id='.$department->id)->assertInertia(fn (Assert $page) => $page->where('records.total', 0));
$csv = $this->get('/relatorios/compras.csv')->assertOk()->streamedContent();
expect($csv)->not->toContain('OTHER-DEPARTMENT-SECRET')->not->toContain('OTHER-COMPANY-SECRET');
});
test('reports reload current access and protect both HTML and CSV from unauthorised profiles and guests', function () {
$this->actingAs($this->f['receiver'])->get('/relatorios')->assertForbidden();
$this->get('/relatorios/compras.csv')->assertForbidden();
$this->actingAs($this->f['issuer']);
DB::table('users')->where('id', $this->f['issuer']->id)->update(['purchasing_role' => 'requester']);
$this->get('/relatorios')->assertForbidden();
$this->get('/relatorios/compras.csv')->assertForbidden();
auth()->logout();
$this->get('/relatorios')->assertRedirect('/login');
$this->get('/relatorios/compras.csv')->assertRedirect('/login');
});
test('reports reject invalid filters on both the screen and export', function () {
foreach (['status=unknown', 'from=invalid', 'from=2026-02-02&to=2026-01-01', 'department_id=-1'] as $filter) {
$this->getJson('/relatorios?'.$filter)->assertUnprocessable();
$this->getJson('/relatorios/compras.csv?'.$filter)->assertUnprocessable();
}
});
test('CSV preserves integer money and escapes formula-like user text', function () {
$this->f['r']->forceFill(['number' => '=SYNTHETIC()', 'total_minor' => 9007199254740993])->save();
$this->f['department']->forceFill(['name' => '+SYNTHETIC'])->save();
$csv = $this->get('/relatorios/compras.csv')->assertOk()->streamedContent();
expect($csv)->toContain("'=SYNTHETIC()")->toContain("'+SYNTHETIC")->toContain('9007199254740993')->not->toContain('9.007');
});
test('individual report follows receipts invoices and payments without exposing bank data or changing balances', function () {
$f = paymentFixture();
$this->actingAs($f['author']);
$workflow = app(ExternalPaymentWorkflow::class);
$payment = $workflow->register($f['author'], $f['invoice'], $f['payment_data']);
$this->actingAs($f['validator']);
$workflow->review($f['validator'], $payment, 'confirmed', 'Conferência independente de teste');
$audits = AuditLog::query()->count();
$balance = $workflow->outstanding($f['invoice']);
$this->get('/requisicoes/'.$f['r']->id.'/relatorio')->assertInertia(fn (Assert $page) => $page
->has('report.fulfilment.receipts', 1)->where('report.fulfilment.finance_visible', true)
->where('report.fulfilment.invoices.0.number', $f['invoice']->number)
->where('report.fulfilment.invoices.0.total_minor', '6000')
->where('report.fulfilment.payments.0.amount_minor', '4000')
->where('report.fulfilment.payments.0.status', 'confirmed')
->missing('report.fulfilment.payments.0.bank_snapshot')
->missing('report.fulfilment.payments.0.external_reference'));
expect(AuditLog::query()->count())->toBe($audits)->and($workflow->outstanding($f['invoice']))->toBe($balance);
$this->actingAs($f['issuer'])->get('/requisicoes/'.$f['r']->id.'/relatorio')->assertInertia(fn (Assert $page) => $page
->has('report.fulfilment.receipts', 1)->where('report.fulfilment.finance_visible', false)
->where('report.fulfilment.invoices', null)->where('report.fulfilment.payments', null));
});