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

108 lines
8.3 KiB
PHP

<?php
use App\Enums\PurchasingRole;
use App\Models\AuditLog;
use App\Models\Department;
use App\Models\Supplier;
use App\Models\SupplierEvaluation;
use App\Models\SupplierImprovement;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Inertia\Testing\AssertableInertia as Assert;
require_once __DIR__.'/../Support/ReceiptFixture.php';
function reportEvaluation(array $fixture, ?int $department = null, int $revision = 1, ?int $previous = null): SupplierEvaluation
{
return SupplierEvaluation::query()->create(['tenant_id' => $fixture['tenant']->id, 'supplier_id' => $fixture['order']->supplier_id, 'department_id' => $department ?? $fixture['r']->department_id,
'created_by' => $fixture['issuer']->id, 'operation_key' => (string) Str::uuid(), 'creation_hash' => str_repeat('b', 64), 'status' => 'approved', 'contributors' => [$fixture['issuer']->id],
'revision' => $revision, 'previous_id' => $previous, 'supplier_snapshot' => ['id' => $fixture['order']->supplier_id, 'name' => 'Fornecedor preservado QA', 'nuit' => '123456789'],
'payload' => ['title' => 'Avaliação QA', 'order_ids' => [], 'criteria' => [], 'period_start' => null, 'period_end' => null]]);
}
function reportMeasure(SupplierEvaluation $evaluation, User $responsible, array $extra = []): SupplierImprovement
{
return SupplierImprovement::query()->create([...['tenant_id' => $evaluation->tenant_id, 'supplier_evaluation_id' => $evaluation->id, 'created_by' => $responsible->id, 'responsible_id' => $responsible->id,
'operation_key' => (string) Str::uuid(), 'creation_hash' => str_repeat('c', 64), 'title' => 'Medida QA', 'description' => 'Objectivo fictício para o relatório.', 'due_on' => '2026-10-29', 'contributors' => [$responsible->id]], ...$extra]);
}
beforeEach(function () {
$this->f = receiptFixture();
$this->actingAs($this->f['issuer']);
$this->evaluation = reportEvaluation($this->f);
$this->base = '/relatorios/medidas-fornecedores';
$this->travelTo(now()->setDate(2026, 10, 30)->startOfDay());
});
test('report paginates all authorized revisions with counts over the entire filtered set', function () {
$next = reportEvaluation($this->f, null, 2, $this->evaluation->id);
foreach (range(1, 27) as $i) {
reportMeasure($i === 27 ? $next : $this->evaluation, $this->f['issuer'], ['title' => 'Medida '.$i, 'status' => $i === 1 ? 'closed' : 'open']);
}
$this->get($this->base)->assertInertia(fn (Assert $p) => $p->component('reports/supplier-improvements')->where('records.total', 27)->has('records.data', 25)->where('counts.open', 26)->where('counts.closed', 1)->where('overdue', 26)->has('suppliers', 1)->has('responsibles', 1)->missing('records.data.0.operation_key')->missing('records.data.0.creation_hash'));
$this->get($this->base.'?page=2')->assertInertia(fn (Assert $p) => $p->has('records.data', 2)->where('records.data.1.revision', 2)->where('records.total', 27));
});
test('deadline boundaries exclude closed and today from overdue and include the full end date', function () {
reportMeasure($this->evaluation, $this->f['issuer'], ['title' => 'Prazo ontem']);
reportMeasure($this->evaluation, $this->f['issuer'], ['title' => 'Prazo hoje', 'due_on' => '2026-10-30', 'status' => 'review']);
reportMeasure($this->evaluation, $this->f['issuer'], ['title' => 'Encerrada', 'due_on' => '2026-10-01', 'status' => 'closed', 'reviewed_at' => '2026-10-20 10:30:00']);
$this->get($this->base.'?overdue=1')->assertInertia(fn (Assert $p) => $p->where('records.total', 1)->where('overdue', 1)->where('records.data.0.title', 'Prazo ontem')->where('records.data.0.overdue', true));
$this->get($this->base.'?from=2026-10-30&to=2026-10-30')->assertInertia(fn (Assert $p) => $p->where('records.total', 1)->where('records.data.0.title', 'Prazo hoje')->where('records.data.0.overdue', false));
$this->get($this->base.'?status=closed&overdue=1')->assertInertia(fn (Assert $p) => $p->where('records.total', 0)->where('overdue', 0));
expect($this->get($this->base.'.csv?status=closed')->assertOk()->streamedContent())->toContain('2026-10-20T10:30:00+00:00');
});
test('supplier assignee state and title filters are combined and invalid ranges refused', function () {
reportMeasure($this->evaluation, $this->f['issuer'], ['title' => 'Referência ESPECIAL', 'status' => 'returned']);
$url = $this->base.'?supplier_id='.$this->evaluation->supplier_id.'&responsible_id='.$this->f['issuer']->id.'&status=returned&search='.urlencode('referência');
$this->get($url)->assertInertia(fn (Assert $p) => $p->where('records.total', 1)->where('counts.returned', 1));
$this->get($url.'&from=2026-11-01')->assertInertia(fn (Assert $p) => $p->where('records.total', 0));
$this->getJson($this->base.'?from=2026-11-01&to=2026-10-01')->assertUnprocessable()->assertJsonValidationErrors('to');
$this->getJson($this->base.'?status=forged')->assertUnprocessable();
$this->getJson($this->base.'.csv?overdue=0')->assertUnprocessable();
});
test('department and company restrictions apply to records counts choices and CSV', function () {
reportMeasure($this->evaluation, $this->f['issuer'], ['title' => 'Visível QA']);
$department = Department::query()->create(['tenant_id' => $this->f['tenant']->id, 'name' => 'Outro departamento']);
$otherEvaluation = reportEvaluation($this->f, $department->id);
$otherResponsible = User::factory()->create(['tenant_id' => $this->f['tenant']->id, 'department_id' => $department->id, 'purchasing_role' => PurchasingRole::Finance, 'name' => 'Responsável não exposto']);
reportMeasure($otherEvaluation, $otherResponsible, ['title' => 'Oculta pelo departamento']);
$foreign = receiptFixture();
reportMeasure(reportEvaluation($foreign), $foreign['issuer'], ['title' => 'Oculta pela empresa']);
$head = User::factory()->create(['tenant_id' => $this->f['tenant']->id, 'department_id' => $this->f['r']->department_id, 'purchasing_role' => PurchasingRole::DepartmentHead]);
$this->actingAs($head)->get($this->base)->assertInertia(fn (Assert $p) => $p->where('records.total', 1)->has('responsibles', 1)->where('responsibles.0.id', $this->f['issuer']->id));
$csv = $this->get($this->base.'.csv')->assertOk()->streamedContent();
expect($csv)->toContain('Visível QA')->not->toContain('Oculta', 'Responsável não exposto');
$this->get($this->base.'?responsible_id='.$otherResponsible->id)->assertInertia(fn (Assert $p) => $p->where('records.total', 0));
});
test('guest forbidden roles and a stale actor role cannot access either representation', function () {
foreach ([$this->base, $this->base.'.csv'] as $url) {
$this->actingAs($this->f['receiver'])->get($url)->assertForbidden();
}
$this->actingAs($this->f['issuer']);
DB::table('users')->where('id', $this->f['issuer']->id)->update(['purchasing_role' => 'requester']);
$this->get($this->base)->assertForbidden();
$this->get($this->base.'.csv')->assertForbidden();
auth()->logout();
$this->get($this->base)->assertRedirect('/login');
});
test('CSV exports beyond the current page in batches and protects formula-like text without writes', function () {
foreach (range(1, 207) as $i) {
reportMeasure($this->evaluation, $this->f['issuer'], ['title' => $i === 1 ? '=HYPERLINK("bad")' : 'Linha '.$i, 'result' => $i === 207 ? '+resultado final' : null, 'evidence' => $i === 207 ? '@referencia' : null]);
}
$supplier = Supplier::query()->findOrFail($this->evaluation->supplier_id)->getAttributes();
$evaluation = $this->evaluation->refresh()->getAttributes();
$audits = AuditLog::query()->count();
$response = $this->get($this->base.'.csv?page=2&status=open')->assertOk();
$csv = $response->streamedContent();
$rows = array_map(fn ($line) => str_getcsv($line, ';', '"', ''), explode("\n", trim($csv)));
expect($rows)->toHaveCount(210)->and($rows[3][4])->toBe('\'=HYPERLINK("bad")')->and($rows[209][4])->toBe('Linha 207')->and($rows[209][11])->toBe('\'+resultado final')->and($rows[209][12])->toBe('\'@referencia');
expect($rows[209][16])->toEndWith('/fornecedores/avaliacoes/'.$this->evaluation->id.'/relatorio#medida-207');
expect(Supplier::query()->findOrFail($this->evaluation->supplier_id)->getAttributes())->toBe($supplier)->and($this->evaluation->refresh()->getAttributes())->toBe($evaluation)->and(AuditLog::query()->count())->toBe($audits);
});