Files
Investidor-V2/tests/Feature/DeskClosedLifecycleTest.php
T

165 lines
11 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\CrmTask;
use App\Models\Lead;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Tests\Support\AuthenticatesQaActors;
use Tests\TestCase;
final class DeskClosedLifecycleTest extends TestCase
{
use AuthenticatesQaActors;
use RefreshDatabase;
private User $owner;
private User $manager;
private string $caseId;
private string $requestId;
protected function setUp(): void
{
parent::setUp();
$this->withoutVite();
Http::fake();
$this->owner = User::factory()->create();
$this->manager = User::factory()->create();
config(['portal.managers' => [$this->manager->email], 'investmoz_ai.enabled' => false, 'suitecrm.enabled' => false]);
$edition = $this->actingAs($this->manager)->putJson('/management/balcao/servicos/project-change', ['base_version' => 0, 'definition' => config('virtual-desk.services.project-change'), 'reason' => 'SYNTHETIC lifecycle QA', 'confirm' => true])->assertCreated()->json('id');
$this->caseId = $this->actingAs($this->owner)->postJson('/balcao-virtual/dossiers', ['title' => 'SYNTHETIC closed dossier', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id');
$this->requestId = $this->postJson('/balcao-virtual/dossiers/'.$this->caseId.'/pedidos', ['service_version_id' => $edition, 'client_key' => (string) Str::uuid()])->assertCreated()->json('id');
}
private function submit(): void
{
$this->putJson('/balcao-virtual/pedidos/'.$this->requestId, ['version' => 1, 'payload' => ['title' => 'SYNTHETIC project', 'description' => 'SYNTHETIC private preparation', 'original_reference' => 'SYNTHETIC-REF', 'change_reason' => 'SYNTHETIC change'], 'submit' => true, 'consent' => true])->assertOk();
}
private function closeFixture(): array
{
DB::table('desk_cases')->where('id', $this->caseId)->update(['closed_at' => now()]);
return ['request' => (array) DB::table('desk_requests')->where('id', $this->requestId)->first(), 'case' => (array) DB::table('desk_cases')->where('id', $this->caseId)->first(), 'events' => DB::table('desk_events')->count(), 'notifications' => DB::table('desk_notifications')->count(), 'messages' => DB::table('desk_messages')->count()];
}
private function assertUnchanged(array $before): void
{
$this->assertSame($before['request'], (array) DB::table('desk_requests')->where('id', $this->requestId)->first());
$this->assertSame($before['case'], (array) DB::table('desk_cases')->where('id', $this->caseId)->first());
foreach (['events' => 'desk_events', 'notifications' => 'desk_notifications', 'messages' => 'desk_messages'] as $key => $table) {
$this->assertSame($before[$key], DB::table($table)->count());
}
}
public function test_closed_dossier_rejects_request_transition_without_changing_its_history(): void
{
$this->submit();
$before = $this->closeFixture();
$this->actingAs($this->manager)->postJson('/balcao-virtual/pedidos/'.$this->requestId.'/estado', ['version' => 2, 'status' => 'reviewing', 'reason' => 'SYNTHETIC review attempt'])->assertConflict();
$this->assertUnchanged($before);
$this->getJson('/balcao-virtual/pedidos/'.$this->requestId)->assertOk()->assertJsonPath('operationsEditable', false)->assertJsonCount(0, 'transitions');
}
public function test_closed_dossier_rejects_stage_changes_but_keeps_workflow_readable(): void
{
$before = $this->closeFixture();
$this->postJson('/balcao-virtual/dossiers/'.$this->caseId.'/etapas', ['version' => 1, 'stage' => 'E0', 'status' => 'preparing', 'reason' => 'SYNTHETIC preparation attempt'])->assertConflict();
$this->assertUnchanged($before);
$this->getJson('/balcao-virtual/dossiers/'.$this->caseId)->assertOk()->assertJsonPath('canWrite', false)->assertJsonStructure(['case' => ['workflow', 'closed_at']]);
}
public function test_closed_dossier_is_excluded_from_triage_and_rejects_assignment(): void
{
$this->submit();
$before = $this->closeFixture();
$this->actingAsWithMfa($this->manager)->postJson('/balcao-virtual/pedidos/'.$this->requestId.'/responsavel', ['version' => 2, 'analyst_id' => $this->manager->id, 'reason' => 'SYNTHETIC assignment attempt'])->assertConflict();
$this->assertUnchanged($before);
$this->getJson('/management/balcao/atribuicoes')->assertOk()->assertJsonCount(0, 'requests');
}
public function test_closed_dossier_refuses_messages_even_if_its_request_is_still_open(): void
{
$this->submit();
$before = $this->closeFixture();
foreach ([$this->owner, $this->manager] as $actor) {
$this->actingAs($actor)->postJson('/balcao-virtual/pedidos/'.$this->requestId.'/mensagens', ['body' => 'SYNTHETIC message attempt', 'client_key' => (string) Str::uuid()])->assertConflict();
}
$this->assertUnchanged($before);
}
public function test_closed_dossier_refuses_task_updates_and_guidance_remains_readable_without_write_actions(): void
{
$view = $this->getJson('/balcao-virtual/dossiers/'.$this->caseId.'/orientacao')->assertOk()->json();
$this->postJson('/balcao-virtual/dossiers/'.$this->caseId.'/recomendacoes', ['code' => 'support_preparation', 'context_hash' => $view['fingerprint'], 'decision' => 'accepted', 'reason' => 'SYNTHETIC preparation intent'])->assertOk();
$task = $this->postJson('/balcao-virtual/dossiers/'.$this->caseId.'/tarefas', ['code' => 'support_preparation', 'context_hash' => $view['fingerprint'], 'consent' => true, 'due_at' => now()->addDays(2)->toIso8601String()])->assertCreated()->json('id');
$before = $this->closeFixture();
foreach ([$this->owner, $this->manager] as $actor) {
$this->actingAs($actor)->postJson('/balcao-virtual/dossiers/'.$this->caseId.'/tarefas/'.$task, ['version' => 1, 'status' => 'done', 'reason' => 'SYNTHETIC task update attempt'])->assertConflict();
}
$this->assertDatabaseHas('tasks', ['id' => $task, 'status' => 'open', 'version_no' => 1]);
$this->assertUnchanged($before);
$this->getJson('/balcao-virtual/dossiers/'.$this->caseId.'/orientacao')->assertOk()->assertJsonPath('can_write', false)->assertJsonPath('can_update_tasks', false)->assertJsonCount(1, 'tasks');
}
public function test_open_dossier_keeps_existing_transition_assignment_message_and_task_permissions(): void
{
$this->submit();
$this->postJson('/balcao-virtual/pedidos/'.$this->requestId.'/estado', ['version' => 2, 'status' => 'reviewing', 'reason' => 'SYNTHETIC self-review'])->assertForbidden();
$this->actingAsWithMfa($this->manager)->postJson('/balcao-virtual/pedidos/'.$this->requestId.'/responsavel', ['version' => 2, 'analyst_id' => $this->manager->id, 'reason' => 'SYNTHETIC permitted assignment'])->assertOk();
$this->postJson('/balcao-virtual/pedidos/'.$this->requestId.'/estado', ['version' => 3, 'status' => 'reviewing', 'reason' => 'SYNTHETIC permitted review'])->assertOk();
$this->actingAs($this->owner)->postJson('/balcao-virtual/pedidos/'.$this->requestId.'/mensagens', ['body' => 'SYNTHETIC permitted message', 'client_key' => (string) Str::uuid()])->assertOk();
$this->getJson('/balcao-virtual/dossiers/'.$this->caseId.'/orientacao')->assertOk()->assertJsonPath('can_write', true)->assertJsonPath('can_update_tasks', true);
$this->actingAs(User::factory()->create())->getJson('/balcao-virtual/pedidos/'.$this->requestId)->assertForbidden();
}
public function test_draft_cancellation_preserves_payload_documents_and_case_and_cannot_cancel_a_submitted_request(): void
{
$before = DB::table('desk_requests')->where('id', $this->requestId)->first();
$this->postJson('/balcao-virtual/pedidos/'.$this->requestId.'/estado', ['version' => 1, 'status' => 'cancelled', 'reason' => 'SYNTHETIC deliberate cancellation'])->assertOk()->assertJsonPath('status', 'cancelled');
$after = DB::table('desk_requests')->where('id', $this->requestId)->first();
$this->assertSame($before->payload, $after->payload);
$this->assertSame($before->submitted_snapshot, $after->submitted_snapshot);
$this->assertDatabaseHas('desk_cases', ['id' => $this->caseId, 'closed_at' => null]);
$this->assertNotNull($after->closed_at);
$this->getJson('/balcao-virtual/pedidos/'.$this->requestId)->assertOk()->assertJsonPath('operationsEditable', false);
$edition = DB::table('desk_requests')->where('id', $this->requestId)->value('service_version_id');
$this->requestId = $this->postJson('/balcao-virtual/dossiers/'.$this->caseId.'/pedidos', ['service_version_id' => $edition, 'client_key' => (string) Str::uuid()])->assertCreated()->json('id');
$this->submit();
$this->postJson('/balcao-virtual/pedidos/'.$this->requestId.'/estado', ['version' => 2, 'status' => 'cancelled', 'reason' => 'SYNTHETIC submitted cancellation attempt'])->assertForbidden();
$this->assertDatabaseHas('desk_requests', ['id' => $this->requestId, 'status' => 'submitted', 'version' => 2]);
}
public function test_dual_linked_crm_task_observes_dossier_closure_without_changing_crm_authority(): void
{
$lead = Lead::create(['title' => 'SYNTHETIC dual-linked lead', 'status' => 'disqualified', 'owner_user_id' => $this->manager->id]);
$task = CrmTask::create(['lead_id' => $lead->id, 'desk_case_id' => $this->caseId, 'guidance_key' => 'synthetic_dual_link', 'title' => 'SYNTHETIC existing linked task', 'status' => 'open', 'assignee_user_id' => $this->manager->id, 'accepted_by' => $this->owner->id, 'accepted_at' => now(), 'external_sync_status' => 'pending_authorization', 'data_classification' => 'confidential']);
$url = '/management/crm/leads/'.$lead->id.'/tasks/'.$task->id;
$this->actingAsWithMfa($this->manager)->patch($url, ['version' => $task->fresh()->getAttribute('version_no'), 'status' => 'in_progress'])->assertRedirect();
$this->assertDatabaseHas('tasks', ['id' => $task->id, 'status' => 'in_progress']);
$this->closeFixture();
$this->patchJson($url, ['version' => $task->fresh()->getAttribute('version_no'), 'status' => 'done'])->assertConflict();
$this->assertDatabaseHas('tasks', ['id' => $task->id, 'status' => 'in_progress']);
$this->get('/management/crm/leads/'.$lead->id)->assertOk()->assertInertia(fn ($page) => $page->where('lead.tasks.0.can_update', false));
}
public function test_crm_without_linked_tasks_does_not_require_an_installed_desk(): void
{
$lead = Lead::create(['title' => 'SYNTHETIC standalone CRM', 'status' => 'disqualified', 'owner_user_id' => $this->manager->id]);
$this->actingAsWithMfa($this->manager);
DB::statement('ALTER TABLE desk_cases RENAME TO desk_cases_qa_unavailable');
try {
$this->get('/management/crm/leads/'.$lead->id)->assertOk()->assertInertia(fn ($page) => $page->where('lead.title', 'SYNTHETIC standalone CRM'));
} finally {
DB::statement('ALTER TABLE desk_cases_qa_unavailable RENAME TO desk_cases');
}
}
}