This commit is contained in:
2026-11-03 11:51:44 +02:00
parent badaada043
commit e753f7a0a4
309 changed files with 14537 additions and 529 deletions
+86
View File
@@ -9,12 +9,15 @@ use App\Models\SupplierResponse;
use App\Models\User;
use App\Services\RequisitionWorkflow;
use App\Services\SupplierResponseWorkflow;
use App\Services\WorkflowTaskInbox;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Inertia\Testing\AssertableInertia as Assert;
use Symfony\Component\HttpKernel\Exception\HttpException;
require_once __DIR__.'/../Support/ReceiptFixture.php';
require_once __DIR__.'/../Support/SupplyFixture.php';
beforeEach(function () {
$this->f = receiptFixture();
@@ -158,3 +161,86 @@ test('failed audit rolls the response edit back without changing the approved ev
expect(fn () => app(SupplierResponseWorkflow::class)->execute($this->f['issuer'], $this->evaluation->id, $record->id, 'save', ['version' => 1, 'payload' => [...$this->payload, 'title' => 'Não deve persistir']]))->toThrow(RuntimeException::class);
expect($record->refresh()->version)->toBe(1)->and($record->payload['title'])->toBe($this->payload['title'])->and($this->evaluation->refresh()->getAttributes())->toBe($before);
});
function qualityResponseTasks(object $test, User $actor): array
{
$test->actingAs($actor);
return array_values(array_filter(app(WorkflowTaskInbox::class)->build($actor, 'procurement'),
fn ($row) => str_starts_with($row['id'], 'supplier-response:')));
}
test('response task follows preparation review return and decision preserving approved source', function () {
$before = $this->evaluation->refresh()->getAttributes();
$record = responseDraft($this);
expect(qualityResponseTasks($this, $this->f['issuer']))->toHaveCount(1)->and(qualityResponseTasks($this, $this->reviewer))->toBe([]);
$this->actingAs($this->f['issuer'])->post($this->url.'/'.$record->id.'/submit', ['version' => 1])->assertRedirect();
expect(qualityResponseTasks($this, $this->f['issuer']))->toBe([]);
$tasks = qualityResponseTasks($this, $this->reviewer);
expect($tasks)->toHaveCount(1)->and($tasks[0]['href'])->toBe($this->base.'#contraditorio-'.$record->id)->and($tasks[0]['due_on'])->toBeNull();
$this->get($this->base)->assertInertia(fn (Assert $p) => $p->where('responses.records.0.canReview', true));
$this->post($this->url.'/'.$record->id.'/return', ['version' => 2, 'reason' => 'Completar a referência da origem dos argumentos.'])->assertRedirect();
expect(qualityResponseTasks($this, $this->reviewer))->toBe([])->and(qualityResponseTasks($this, $this->f['issuer']))->toHaveCount(1);
$this->post($this->url.'/'.$record->id.'/submit', ['version' => 3])->assertRedirect();
$this->actingAs($this->reviewer)->post($this->url.'/'.$record->id.'/decide', ['version' => 4, ...$this->decision])->assertRedirect();
expect(qualityResponseTasks($this, $this->reviewer))->toBe([])->and(qualityResponseTasks($this, $this->f['issuer']))->toBe([])
->and($this->evaluation->fresh()->getAttributes())->toBe($before);
});
test('response task excludes every contributor and does not disclose arguments or evidence', function () {
$record = responseDraft($this);
$this->actingAs($this->reviewer)->post($this->url.'/'.$record->id.'/save', ['version' => 1, 'payload' => [...$this->payload, 'statement' => 'Argumentos actualizados por outro preparador.']])->assertRedirect();
expect(qualityResponseTasks($this, $this->reviewer))->toHaveCount(1);
$this->post($this->url.'/'.$record->id.'/submit', ['version' => 2])->assertRedirect();
expect(qualityResponseTasks($this, $this->reviewer))->toBe([])->and(qualityResponseTasks($this, $this->f['issuer']))->toBe([]);
$independent = User::factory()->create(['tenant_id' => $this->f['tenant']->id, 'purchasing_role' => PurchasingRole::Finance]);
$tasks = qualityResponseTasks($this, $independent);
expect($tasks)->toHaveCount(1)->and(array_key_exists('payload', $tasks[0]))->toBeFalse()->and(array_key_exists('decision', $tasks[0]))->toBeFalse();
});
test('response task excludes foreign company department current conflicts and maintenance', function () {
$record = responseInReview($this);
$other = receiptFixture();
expect(qualityResponseTasks($this, $other['issuer']))->toBe([]);
$dept = Department::query()->create(['tenant_id' => $this->f['tenant']->id, 'name' => 'Outro âmbito']);
$head = User::factory()->create(['tenant_id' => $this->f['tenant']->id, 'department_id' => $dept->id, 'purchasing_role' => PurchasingRole::DepartmentHead]);
expect(qualityResponseTasks($this, $head))->toBe([]);
supplyGrant($this->reviewer, ['finance']);
DB::table('organization_memberships')->where('user_id', $this->reviewer->id)->update(['maintenance' => true, 'expires_at' => now()->addHour()]);
expect(qualityResponseTasks($this, $this->reviewer))->toBe([]);
$this->post($this->url.'/'.$record->id.'/decide', ['version' => 2, ...$this->decision])->assertForbidden();
DB::table('organization_memberships')->where('user_id', $this->reviewer->id)->update(['maintenance' => false]);
ConflictDeclaration::query()->create(['tenant_id' => $this->f['tenant']->id, 'user_id' => $this->reviewer->id, 'requisition_id' => $this->f['r']->id,
'has_conflict' => true, 'known_since' => today()->toDateString(), 'declared_at' => now(), 'reason' => 'Conflito fictício nos argumentos do fornecedor.']);
expect(qualityResponseTasks($this, $this->reviewer))->toBe([]);
$this->get($this->base)->assertInertia(fn (Assert $p) => $p->where('responses.records.0.canReview', false));
});
test('response task keeps old pending records beyond one hundred and pages them without writes', function () {
$record = responseInReview($this);
for ($i = 0; $i < 104; $i++) {
$copy = $record->replicate();
$copy->operation_key = (string) Str::uuid();
$copy->save();
}
$before = AuditLog::query()->count();
expect(qualityResponseTasks($this, $this->reviewer))->toHaveCount(105);
$this->get('/requisicoes/tarefas?module=purchases&search=CTR-')->assertInertia(fn (Assert $p) => $p->where('tasks.total', 105)->has('tasks.data', 25)->where('tasks.data.0.id', 'supplier-response:'.$record->id.':review:2'));
$this->get('/requisicoes/tarefas?module=purchases&search=CTR-&page=5')->assertInertia(fn (Assert $p) => $p->has('tasks.data', 5));
expect(AuditLog::query()->count())->toBe($before);
});
test('response task and commands reject a revoked role even with a previously loaded actor', function () {
$record = responseInReview($this);
expect(qualityResponseTasks($this, $this->reviewer))->toHaveCount(1);
DB::table('users')->where('id', $this->reviewer->id)->update(['purchasing_role' => 'requester']);
expect(qualityResponseTasks($this, $this->reviewer->fresh()))->toBe([]);
$this->actingAs($this->reviewer);
try {
app(SupplierResponseWorkflow::class)->execute($this->reviewer, $this->evaluation->id, $record->id, 'decide', ['version' => 2, ...$this->decision]);
$this->fail('O papel revogado não pode decidir.');
} catch (HttpException $exception) {
expect($exception->getStatusCode())->toBe(403);
}
expect($record->refresh()->status)->toBe('review')->and($record->version)->toBe(2);
});