268 lines
22 KiB
PHP
268 lines
22 KiB
PHP
<?php
|
|
|
|
use App\Enums\PurchasingRole;
|
|
use App\Models\AuditLog;
|
|
use App\Models\ConflictDeclaration;
|
|
use App\Models\CostCenter;
|
|
use App\Models\Department;
|
|
use App\Models\ProposalEvaluation;
|
|
use App\Models\Quotation;
|
|
use App\Models\Requisition;
|
|
use App\Models\Supplier;
|
|
use App\Models\Tenant;
|
|
use App\Models\User;
|
|
use App\Services\ConflictGuard;
|
|
use App\Services\RequisitionWorkflow;
|
|
use Inertia\Testing\AssertableInertia as Assert;
|
|
|
|
function personalConflict(int $supplierId, ?int $requisitionId = null, bool $hasConflict = true): array
|
|
{
|
|
return ['scope' => $requisitionId ? 'process_supplier' : 'supplier', 'supplier_id' => $supplierId, 'requisition_id' => $requisitionId, 'has_conflict' => $hasConflict, 'known_since' => now()->subYears(3)->format('Y-m-d'), 'reason' => 'Relação pessoal relevante declarada para efeitos de transparência.'];
|
|
}
|
|
|
|
beforeEach(function () {
|
|
$this->tenant = Tenant::query()->create(['name' => 'Conflitos A']);
|
|
$this->department = Department::query()->create(['tenant_id' => $this->tenant->id, 'name' => 'Operações']);
|
|
$this->center = CostCenter::query()->create(['tenant_id' => $this->tenant->id, 'department_id' => $this->department->id, 'code' => 'OPS', 'name' => 'Operações']);
|
|
foreach (['owner' => PurchasingRole::Requester, 'buyer' => PurchasingRole::Procurement, 'head' => PurchasingRole::DepartmentHead, 'finance' => PurchasingRole::Finance, 'admin' => PurchasingRole::Admin] as $key => $role) {
|
|
$this->{$key} = User::factory()->create(['tenant_id' => $this->tenant->id, 'department_id' => $this->department->id, 'purchasing_role' => $role]);
|
|
}
|
|
$this->actingAs($this->buyer);
|
|
$supplierData = ['tenant_id' => $this->tenant->id, 'created_by' => $this->buyer->id, 'name' => 'Fornecedor A', 'nuit' => '123456789', 'contact' => 'Contacto', 'email' => 'contacto@example.test', 'phone' => '123456789', 'address' => 'Maputo', 'category' => 'Material', 'beneficiaries' => 'Beneficiário de teste', 'document_references' => 'Referências documentais do processo.', 'diligence_notes' => 'Documentação de teste conferida.', 'status' => 'active', 'next_review' => now()->addYear()];
|
|
$this->supplier = Supplier::query()->create($supplierData);
|
|
$this->otherSupplier = Supplier::query()->create([...$supplierData, 'name' => 'Fornecedor B', 'nuit' => '987654321']);
|
|
$this->r = Requisition::query()->create(['tenant_id' => $this->tenant->id, 'requester_id' => $this->owner->id, 'department_id' => $this->department->id, 'cost_center_id' => $this->center->id, 'number' => 'REQ-CONFLICT', 'priority' => 'normal', 'needed_by' => now()->addWeek(), 'justification' => 'Pedido de teste de conflitos.', 'status' => 'approved', 'subtotal_minor' => 10000, 'tax_minor' => 0, 'total_minor' => 10000]);
|
|
$this->item = $this->r->items()->create(['tenant_id' => $this->tenant->id, 'description' => 'Material', 'category' => 'Material', 'purchase_type' => 'consumable', 'quantity' => '1', 'unit' => 'un', 'unit_price_minor' => 10000, 'tax_rate' => '0', 'subtotal_minor' => 10000, 'tax_minor' => 0, 'total_minor' => 10000]);
|
|
$this->quote = ['supplier_id' => $this->supplier->id, 'reference' => 'PROP-CONFLICT', 'delivery_days' => 5, 'valid_until' => now()->addMonth()->format('Y-m-d'), 'payment_terms' => '30 dias', 'no_conflict' => true, 'items' => [['requisition_item_id' => $this->item->id, 'unit_price' => '100.00']]];
|
|
$this->evaluation = ['quality' => '8', 'history' => '5', 'payment' => '7', 'comment' => 'Avaliação independente para testes.', 'no_conflict' => true];
|
|
});
|
|
|
|
test('every purchasing profile can make a personal declaration with server controlled authorship', function (PurchasingRole $role) {
|
|
$user = User::factory()->create(['tenant_id' => $this->tenant->id, 'purchasing_role' => $role]);
|
|
$this->actingAs($user)->post('/conflitos', personalConflict($this->supplier->id))->assertSessionHasNoErrors()->assertRedirect('/conflitos');
|
|
$declaration = ConflictDeclaration::query()->firstOrFail();
|
|
expect($declaration->user_id)->toBe($user->id)->and($declaration->tenant_id)->toBe($this->tenant->id);
|
|
expect($declaration->declared_at)->not->toBeNull();
|
|
expect(AuditLog::query()->where('action', 'conflict.declared')->count())->toBe(1);
|
|
$this->post('/conflitos', [...personalConflict($this->supplier->id), 'user_id' => $this->owner->id])->assertSessionHasErrors('user_id');
|
|
})->with(array_values(array_filter(PurchasingRole::cases(), fn (PurchasingRole $role) => $role !== PurchasingRole::Configured)));
|
|
|
|
test('consultation is limited to personal declarations except finance and administration oversight', function (PurchasingRole $role, int $expected) {
|
|
$this->actingAs($this->owner)->post('/conflitos', personalConflict($this->supplier->id));
|
|
$declaration = ConflictDeclaration::query()->firstOrFail();
|
|
$user = User::factory()->create(['tenant_id' => $this->tenant->id, 'department_id' => $this->department->id, 'purchasing_role' => $role]);
|
|
$this->actingAs($user)->get('/conflitos')->assertInertia(fn (Assert $page) => $page->component('conflicts/index')->has('declarations.data', $expected)->missing('suppliers.0.beneficiaries')->missing('suppliers.0.email'));
|
|
if ($expected === 1) {
|
|
$this->get('/conflitos/'.$declaration->id)->assertInertia(fn (Assert $page) => $page->component('conflicts/show')->where('canCorrect', false));
|
|
} else {
|
|
$this->get('/conflitos/'.$declaration->id)->assertNotFound();
|
|
}
|
|
$this->put('/conflitos/'.$declaration->id, ['reason' => 'Tentativa de corrigir declaração alheia.', 'known_since' => now()->format('Y-m-d')])->assertForbidden();
|
|
})->with([[PurchasingRole::Admin, 1], [PurchasingRole::Finance, 1], [PurchasingRole::Procurement, 0], [PurchasingRole::DepartmentHead, 0], [PurchasingRole::Requester, 0], [PurchasingRole::Warehouse, 0]]);
|
|
|
|
test('corrections append history preserve the original assertion and reject stale edits', function () {
|
|
$this->post('/conflitos', personalConflict($this->supplier->id));
|
|
$original = ConflictDeclaration::query()->firstOrFail();
|
|
$before = $original->toArray();
|
|
$correction = ['reason' => 'Fundamento corrigido com informação complementar.', 'known_since' => now()->subYear()->format('Y-m-d')];
|
|
$this->put('/conflitos/'.$original->id, $correction)->assertSessionHasNoErrors();
|
|
expect($original->fresh()->toArray())->toBe($before);
|
|
$latest = ConflictDeclaration::query()->latest('id')->firstOrFail();
|
|
expect($latest->previous_id)->toBe($original->id)->and($latest->root_id)->toBe($original->id)->and($latest->has_conflict)->toBeTrue()->and($latest->user_id)->toBe($this->buyer->id);
|
|
$audit = AuditLog::query()->where('action', 'conflict.corrected')->firstOrFail();
|
|
expect($audit->before['reason'])->toBe($before['reason'])->and($audit->after['reason'])->toBe($correction['reason']);
|
|
$this->put('/conflitos/'.$original->id, $correction)->assertSessionHasErrors('history');
|
|
$this->get('/conflitos/'.$original->id)->assertInertia(fn (Assert $page) => $page->has('history', 2)->where('canCorrect', true));
|
|
$this->put('/conflitos/'.$latest->id, [...$correction, 'reason' => 'Segunda correcção pessoal do fundamento.'])->assertSessionHasNoErrors();
|
|
$this->get('/conflitos/'.$original->id)->assertInertia(fn (Assert $page) => $page->has('history', 3));
|
|
});
|
|
|
|
test('corrections cannot remove conflicts change scope author or company', function (string $field, mixed $value) {
|
|
$this->post('/conflitos', personalConflict($this->supplier->id));
|
|
$declaration = ConflictDeclaration::query()->firstOrFail();
|
|
$this->put('/conflitos/'.$declaration->id, ['reason' => 'Tentativa de alterar campos protegidos.', 'known_since' => now()->format('Y-m-d'), $field => $value])->assertSessionHasErrors($field);
|
|
expect(ConflictDeclaration::query()->count())->toBe(1);
|
|
})->with([['has_conflict', false], ['supplier_id', 999], ['requisition_id', 999], ['user_id', 999], ['tenant_id', 999], ['declared_at', '2020-01-01'], ['previous_id', 999], ['root_id', 999]]);
|
|
|
|
test('declaration dates describe knowledge without automatic expiry and absence cannot override a conflict', function () {
|
|
$this->post('/conflitos', personalConflict($this->supplier->id))->assertSessionHasNoErrors();
|
|
$this->post('/conflitos', personalConflict($this->supplier->id, null, false))->assertSessionHasNoErrors();
|
|
$this->post('/conflitos', [...personalConflict($this->supplier->id), 'known_since' => now()->addDay()->format('Y-m-d')])->assertSessionHasErrors('known_since');
|
|
expect(app(ConflictGuard::class)->hasConflict($this->buyer->id, $this->supplier->id, $this->r->id))->toBeTrue();
|
|
$this->post('/requisicoes/'.$this->r->id.'/cotacoes', $this->quote)->assertSessionHasErrors('conflict');
|
|
expect(Quotation::query()->count())->toBe(0);
|
|
});
|
|
|
|
test('absence alone does not prevent legitimate participation', function () {
|
|
$this->post('/conflitos', personalConflict($this->supplier->id, null, false));
|
|
$this->post('/requisicoes/'.$this->r->id.'/cotacoes', $this->quote)->assertSessionHasNoErrors();
|
|
expect(Quotation::query()->count())->toBe(1);
|
|
});
|
|
|
|
test('unknown supplier does not block a request but association rechecks its requester', function () {
|
|
$this->actingAs($this->owner)->post('/conflitos', personalConflict($this->supplier->id));
|
|
$payload = ['cost_center_id' => $this->center->id, 'priority' => 'normal', 'needed_by' => now()->addWeek()->format('Y-m-d'), 'justification' => 'Requisição criada sem fornecedor definido.', 'items' => [['description' => 'Material', 'category' => 'Material', 'purchase_type' => 'consumable', 'quantity' => '1', 'unit' => 'un', 'unit_price' => '100', 'tax_rate' => '0']]];
|
|
$this->post('/requisicoes', $payload)->assertSessionHasNoErrors();
|
|
$r = Requisition::query()->latest('id')->firstOrFail();
|
|
$this->put('/requisicoes/'.$r->id, $payload)->assertSessionHasNoErrors();
|
|
$this->post('/requisicoes/'.$r->id.'/submeter')->assertSessionHasNoErrors();
|
|
$this->actingAs($this->head)->post('/requisicoes/'.$r->id.'/decidir', ['decision' => 'approved'])->assertSessionHasNoErrors();
|
|
expect($r->refresh()->status)->toBe('approved');
|
|
$quote = [...$this->quote, 'items' => [['requisition_item_id' => $r->items()->firstOrFail()->id, 'unit_price' => '100']]];
|
|
$this->actingAs($this->buyer)->post('/requisicoes/'.$r->id.'/cotacoes', $quote)->assertSessionHasErrors('conflict');
|
|
$this->post('/requisicoes/'.$r->id.'/cotacoes', [...$quote, 'supplier_id' => $this->otherSupplier->id])->assertSessionHasNoErrors();
|
|
});
|
|
|
|
test('supplier association checks prior approvers even if their role has changed', function () {
|
|
$this->r->steps()->create(['tenant_id' => $this->tenant->id, 'position' => 1, 'role' => 'department_head', 'decision' => 'approved', 'approver_id' => $this->head->id]);
|
|
$this->actingAs($this->head)->post('/conflitos', personalConflict($this->supplier->id));
|
|
$this->head->forceFill(['purchasing_role' => PurchasingRole::Warehouse])->save();
|
|
$this->actingAs($this->buyer)->post('/requisicoes/'.$this->r->id.'/cotacoes', $this->quote)->assertSessionHasErrors('conflict');
|
|
expect(Quotation::query()->count())->toBe(0);
|
|
});
|
|
|
|
test('new associations recheck earlier quotation authors and evaluators', function (string $participant) {
|
|
$this->post('/requisicoes/'.$this->r->id.'/cotacoes', [...$this->quote, 'supplier_id' => $this->otherSupplier->id])->assertSessionHasNoErrors();
|
|
$quote = Quotation::query()->firstOrFail();
|
|
$actor = $participant === 'author' ? $this->buyer : $this->finance;
|
|
if ($participant === 'evaluator') {
|
|
$this->actingAs($actor)->post('/cotacoes/'.$quote->id.'/avaliar', $this->evaluation)->assertSessionHasNoErrors();
|
|
}
|
|
$this->actingAs($actor)->post('/conflitos', personalConflict($this->supplier->id))->assertSessionHasNoErrors();
|
|
$newBuyer = User::factory()->create(['tenant_id' => $this->tenant->id, 'department_id' => $this->department->id, 'purchasing_role' => PurchasingRole::Procurement]);
|
|
$this->actingAs($newBuyer)->post('/requisicoes/'.$this->r->id.'/cotacoes', $this->quote)->assertSessionHasErrors('conflict');
|
|
expect(Quotation::query()->count())->toBe(1);
|
|
})->with(['author', 'evaluator']);
|
|
|
|
test('historical audit participants are rechecked at supplier association', function () {
|
|
$formerParticipant = User::factory()->create(['tenant_id' => $this->tenant->id, 'purchasing_role' => PurchasingRole::Requester]);
|
|
$this->actingAs($formerParticipant)->post('/conflitos', personalConflict($this->supplier->id));
|
|
AuditLog::query()->create(['tenant_id' => $this->tenant->id, 'user_id' => $formerParticipant->id, 'requisition_id' => $this->r->id, 'action' => 'requisition.updated', 'after' => ['status' => 'draft']]);
|
|
$this->actingAs($this->buyer)->post('/requisicoes/'.$this->r->id.'/cotacoes', $this->quote)->assertSessionHasErrors('conflict');
|
|
});
|
|
|
|
test('process supplier declarations only affect their declared process and supplier', function () {
|
|
$this->post('/conflitos', personalConflict($this->supplier->id, $this->r->id))->assertSessionHasNoErrors();
|
|
$guard = app(ConflictGuard::class);
|
|
expect($guard->hasConflict($this->buyer->id, $this->supplier->id, $this->r->id))->toBeTrue();
|
|
expect($guard->hasConflict($this->buyer->id, $this->otherSupplier->id, $this->r->id))->toBeFalse();
|
|
expect($guard->hasConflict($this->buyer->id, $this->supplier->id, $this->r->id + 1))->toBeFalse();
|
|
$this->post('/requisicoes/'.$this->r->id.'/cotacoes', [...$this->quote, 'supplier_id' => $this->otherSupplier->id])->assertSessionHasNoErrors();
|
|
});
|
|
|
|
test('a process wide conflict blocks participation regardless of candidate supplier', function () {
|
|
$this->post('/conflitos', [...personalConflict($this->supplier->id, $this->r->id), 'scope' => 'process', 'supplier_id' => null])->assertSessionHasNoErrors();
|
|
$this->post('/requisicoes/'.$this->r->id.'/cotacoes', $this->quote)->assertSessionHasErrors('conflict');
|
|
$this->post('/requisicoes/'.$this->r->id.'/cotacoes', [...$this->quote, 'supplier_id' => $this->otherSupplier->id])->assertSessionHasErrors('conflict');
|
|
});
|
|
|
|
test('process declarations only refer to processes visible to their author', function () {
|
|
$outsider = User::factory()->create(['tenant_id' => $this->tenant->id, 'department_id' => $this->department->id, 'purchasing_role' => PurchasingRole::Requester]);
|
|
$this->actingAs($outsider)->get('/conflitos')->assertInertia(fn (Assert $page) => $page->has('requisitions', 0));
|
|
$this->post('/conflitos', personalConflict($this->supplier->id, $this->r->id))->assertNotFound();
|
|
});
|
|
|
|
test('direct evaluations cannot bypass active conflict with an absence checkbox', function () {
|
|
$this->post('/requisicoes/'.$this->r->id.'/cotacoes', $this->quote);
|
|
$q = Quotation::query()->firstOrFail();
|
|
$this->actingAs($this->finance)->post('/conflitos', personalConflict($this->supplier->id));
|
|
$this->post('/cotacoes/'.$q->id.'/avaliar', $this->evaluation)->assertSessionHasErrors('conflict');
|
|
expect(ProposalEvaluation::query()->count())->toBe(0);
|
|
$this->get('/requisicoes/'.$this->r->id.'/cotacoes')->assertInertia(fn (Assert $page) => $page->where('quotations.0.conflict_blocked', true)->where('canEvaluate', false));
|
|
});
|
|
|
|
test('late conflicts keep past evaluations and invalidate comparison readiness', function () {
|
|
$supplierData = $this->supplier->only(['tenant_id', 'created_by', 'contact', 'email', 'phone', 'address', 'category', 'beneficiaries', 'document_references', 'diligence_notes', 'status', 'next_review']);
|
|
$third = Supplier::query()->create([...$supplierData, 'name' => 'Fornecedor C', 'nuit' => '111222333']);
|
|
foreach ([$this->supplier, $this->otherSupplier, $third] as $supplier) {
|
|
$this->actingAs($this->buyer)->post('/requisicoes/'.$this->r->id.'/cotacoes', [...$this->quote, 'supplier_id' => $supplier->id])->assertSessionHasNoErrors();
|
|
$q = Quotation::query()->latest('id')->firstOrFail();
|
|
$this->post('/cotacoes/'.$q->id.'/avaliar', $this->evaluation)->assertSessionHasNoErrors();
|
|
$this->actingAs($this->finance)->post('/cotacoes/'.$q->id.'/avaliar', $this->evaluation)->assertSessionHasNoErrors();
|
|
}
|
|
$this->get('/requisicoes/'.$this->r->id.'/cotacoes')->assertInertia(fn (Assert $page) => $page->where('ready', true));
|
|
$this->post('/conflitos', personalConflict($this->supplier->id))->assertSessionHasNoErrors();
|
|
$this->post('/conflitos', personalConflict($this->supplier->id, null, false))->assertSessionHasNoErrors();
|
|
$this->get('/requisicoes/'.$this->r->id.'/cotacoes')->assertInertia(fn (Assert $page) => $page->where('ready', false)->where('quotations.0.eligible', false)->where('quotations.0.score', null)->has('quotations.0.evaluations', 2));
|
|
expect(ProposalEvaluation::query()->count())->toBe(6);
|
|
expect(AuditLog::query()->where('action', 'quotation.evaluated')->count())->toBe(6);
|
|
});
|
|
|
|
test('known supplier conflicts block pending approvals on the server', function () {
|
|
$this->post('/requisicoes/'.$this->r->id.'/cotacoes', $this->quote);
|
|
$this->r->update(['status' => 'pending']);
|
|
$this->r->steps()->create(['tenant_id' => $this->tenant->id, 'position' => 1, 'role' => 'department_head']);
|
|
$this->actingAs($this->head)->post('/conflitos', personalConflict($this->supplier->id));
|
|
$this->get('/requisicoes/'.$this->r->id)->assertInertia(fn (Assert $page) => $page->where('canDecide', false)->where('conflictBlocked', true));
|
|
$this->post('/requisicoes/'.$this->r->id.'/decidir', ['decision' => 'approved'])->assertForbidden();
|
|
expect($this->r->refresh()->status)->toBe('pending');
|
|
expect($this->r->steps()->firstOrFail()->decision)->toBe('pending');
|
|
});
|
|
|
|
test('a conflict declared by an earlier requester blocks subsequent approval by a different person', function () {
|
|
$this->post('/requisicoes/'.$this->r->id.'/cotacoes', $this->quote)->assertSessionHasNoErrors();
|
|
$this->r->update(['status' => 'pending']);
|
|
$this->r->steps()->create(['tenant_id' => $this->tenant->id, 'position' => 1, 'role' => 'department_head']);
|
|
$this->actingAs($this->owner)->post('/conflitos', personalConflict($this->supplier->id));
|
|
$this->actingAs($this->head)->post('/requisicoes/'.$this->r->id.'/decidir', ['decision' => 'approved'])->assertForbidden();
|
|
expect($this->r->steps()->firstOrFail()->decision)->toBe('pending');
|
|
});
|
|
|
|
test('scope requires the corresponding existing supplier and process', function () {
|
|
$this->post('/conflitos', [...personalConflict($this->supplier->id), 'supplier_id' => null])->assertSessionHasErrors('supplier_id');
|
|
$this->post('/conflitos', [...personalConflict($this->supplier->id), 'scope' => 'process', 'supplier_id' => null])->assertSessionHasErrors('requisition_id');
|
|
$this->post('/conflitos', [...personalConflict($this->supplier->id, $this->r->id), 'scope' => 'supplier'])->assertSessionHasErrors('requisition_id');
|
|
expect(ConflictDeclaration::query()->count())->toBe(0);
|
|
});
|
|
|
|
test('correction audit failure preserves the original and allows a later valid correction', function () {
|
|
$this->post('/conflitos', personalConflict($this->supplier->id));
|
|
$declaration = ConflictDeclaration::query()->firstOrFail();
|
|
$this->mock(RequisitionWorkflow::class, function ($mock) {
|
|
$mock->shouldReceive('audit')->once()->andThrow(new RuntimeException('Falha de auditoria da correcção.'));
|
|
});
|
|
$this->put('/conflitos/'.$declaration->id, ['reason' => 'Correcção que não pode ficar parcialmente gravada.', 'known_since' => now()->format('Y-m-d')])->assertStatus(500);
|
|
expect(ConflictDeclaration::query()->count())->toBe(1)->and($declaration->successor()->exists())->toBeFalse();
|
|
expect(AuditLog::query()->where('action', 'conflict.corrected')->count())->toBe(0);
|
|
});
|
|
|
|
test('process conflict blocks submission and editing without erasing the draft', function () {
|
|
$this->r->update(['status' => 'draft']);
|
|
$this->actingAs($this->owner)->post('/conflitos', [...personalConflict($this->supplier->id, $this->r->id), 'scope' => 'process', 'supplier_id' => null]);
|
|
$this->post('/requisicoes/'.$this->r->id.'/submeter')->assertSessionHasErrors('conflict');
|
|
expect($this->r->refresh()->status)->toBe('draft')->and($this->r->items()->count())->toBe(1);
|
|
});
|
|
|
|
test('declarations and all changes isolate companies', function () {
|
|
$this->post('/conflitos', personalConflict($this->supplier->id, $this->r->id));
|
|
$declaration = ConflictDeclaration::query()->firstOrFail();
|
|
$other = Tenant::query()->create(['name' => 'Conflitos B']);
|
|
$admin = User::factory()->create(['tenant_id' => $other->id, 'purchasing_role' => PurchasingRole::Admin]);
|
|
$this->actingAs($admin)->get('/conflitos')->assertInertia(fn (Assert $page) => $page->has('declarations.data', 0)->has('suppliers', 0)->has('requisitions', 0));
|
|
$this->get('/conflitos/'.$declaration->id)->assertNotFound();
|
|
$this->put('/conflitos/'.$declaration->id, ['reason' => 'Tentativa de outro inquilino.', 'known_since' => now()->format('Y-m-d')])->assertNotFound();
|
|
$this->post('/conflitos', personalConflict($this->supplier->id))->assertSessionHasErrors('supplier_id');
|
|
$this->post('/conflitos', [...personalConflict($this->supplier->id, $this->r->id), 'scope' => 'process', 'supplier_id' => null])->assertSessionHasErrors('requisition_id');
|
|
});
|
|
|
|
test('failed audit rolls back declaration and creates no partial blocking record', function () {
|
|
$this->mock(RequisitionWorkflow::class, function ($mock) {
|
|
$mock->shouldReceive('audit')->once()->andThrow(new RuntimeException('Falha de auditoria simulada.'));
|
|
});
|
|
$this->post('/conflitos', personalConflict($this->supplier->id))->assertStatus(500);
|
|
expect(ConflictDeclaration::query()->count())->toBe(0);
|
|
});
|
|
|
|
test('declaration model rejects destructive mutation and deletion', function () {
|
|
$this->post('/conflitos', personalConflict($this->supplier->id));
|
|
$declaration = ConflictDeclaration::query()->firstOrFail();
|
|
expect(fn () => $declaration->update(['has_conflict' => false]))->toThrow(LogicException::class);
|
|
expect(fn () => $declaration->delete())->toThrow(LogicException::class);
|
|
});
|
|
|
|
test('guests cannot consult or submit declarations', function () {
|
|
auth()->logout();
|
|
$this->get('/conflitos')->assertRedirect('/login');
|
|
$this->post('/conflitos', personalConflict($this->supplier->id))->assertRedirect('/login');
|
|
});
|