133 lines
8.5 KiB
PHP
133 lines
8.5 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\CrmTask;
|
|
use App\Models\User;
|
|
use App\Services\VirtualDesk;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Testing\TestResponse;
|
|
use Tests\TestCase;
|
|
|
|
final class DeskCaseStateTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private User $owner;
|
|
|
|
private User $manager;
|
|
|
|
private string $caseId;
|
|
|
|
private string $requestId;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->withoutVite();
|
|
$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'), 'confirm' => true, 'reason' => 'SYNTHETIC owner lifecycle QA'])->assertCreated()->json('id');
|
|
$this->caseId = $this->actingAs($this->owner)->postJson('/balcao-virtual/dossiers', ['title' => 'SYNTHETIC owned 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 operation(string $state, int $version): array
|
|
{
|
|
return ['state' => $state, 'version' => $version, 'reason' => 'SYNTHETIC owner decision '.$state, 'client_key' => (string) Str::uuid()];
|
|
}
|
|
|
|
private function change(array $input): TestResponse
|
|
{
|
|
return $this->putJson('/balcao-virtual/dossiers/'.$this->caseId.'/estado', $input);
|
|
}
|
|
|
|
public function test_owner_closes_and_reopens_without_changing_payload_workflow_or_child_states(): void
|
|
{
|
|
$task = CrmTask::create(['desk_case_id' => $this->caseId, 'guidance_key' => 'synthetic_open_task', 'title' => 'SYNTHETIC accepted task', 'status' => 'open', 'accepted_by' => $this->owner->id, 'accepted_at' => now(), 'assignee_user_id' => $this->owner->id, 'external_sync_status' => 'pending_authorization', 'data_classification' => 'confidential']);
|
|
$payment = (string) Str::uuid();
|
|
DB::table('desk_payments')->insert(['id' => $payment, 'request_id' => $this->requestId, 'reference' => 'SYNTHETIC-PENDING', 'amount' => '25.50', 'currency' => 'MZN', 'source_reference' => 'SYNTHETIC pending declaration', 'status' => 'pending']);
|
|
$before = DB::table('desk_cases')->where('id', $this->caseId)->first();
|
|
$request = (array) DB::table('desk_requests')->where('id', $this->requestId)->first();
|
|
$this->getJson('/balcao-virtual/dossiers/'.$this->caseId)->assertOk()->assertJsonPath('canChangeState', true)->assertJsonPath('activity.open_requests', 1)->assertJsonPath('activity.open_tasks', 1)->assertJsonPath('activity.pending_payments', 1);
|
|
$this->change($this->operation('closed', 1))->assertOk()->assertJsonPath('version', 2)->assertJsonPath('replayed', false);
|
|
$closed = DB::table('desk_cases')->where('id', $this->caseId)->first();
|
|
$this->assertNotNull($closed->closed_at);
|
|
$this->assertSame($before->payload, $closed->payload);
|
|
$this->assertSame($before->workflow, $closed->workflow);
|
|
$this->getJson('/balcao-virtual/pedidos/'.$this->requestId)->assertOk()->assertJsonPath('operationsEditable', false)->assertJsonPath('request.status', 'draft');
|
|
$this->change($this->operation('open', 2))->assertOk()->assertJsonPath('version', 3)->assertJsonPath('closed_at', null);
|
|
$this->assertSame($request, (array) DB::table('desk_requests')->where('id', $this->requestId)->first());
|
|
$this->assertDatabaseHas('tasks', ['id' => $task->id, 'status' => 'open', 'version_no' => 1]);
|
|
$this->assertDatabaseHas('desk_payments', ['id' => $payment, 'status' => 'pending', 'amount' => '25.50', 'version' => 1]);
|
|
$this->getJson('/balcao-virtual/pedidos/'.$this->requestId)->assertOk()->assertJsonPath('fieldsEditable', true);
|
|
}
|
|
|
|
public function test_manager_or_other_organization_writer_is_not_the_registered_owner(): void
|
|
{
|
|
$this->actingAs($this->manager)->change($this->operation('closed', 1))->assertForbidden();
|
|
$org = (string) Str::uuid();
|
|
DB::table('organizations')->insert(['id' => $org, 'name' => 'SYNTHETIC owner organization', 'organization_type' => 'private']);
|
|
DB::table('desk_cases')->where('id', $this->caseId)->update(['organization_id' => $org]);
|
|
foreach ([$this->owner, $this->manager] as $member) {
|
|
DB::table('memberships')->insert(['id' => (string) Str::uuid(), 'organization_id' => $org, 'user_id' => $member->id, 'role' => 'admin']);
|
|
}
|
|
$this->getJson('/balcao-virtual/dossiers/'.$this->caseId)->assertOk()->assertJsonPath('canWrite', true)->assertJsonPath('canChangeState', false);
|
|
$this->change($this->operation('closed', 1))->assertForbidden();
|
|
$this->actingAs($this->owner)->change($this->operation('closed', 1))->assertOk();
|
|
DB::table('memberships')->where('organization_id', $org)->where('user_id', $this->owner->id)->update(['deleted_at' => now()]);
|
|
$this->change($this->operation('open', 2))->assertForbidden();
|
|
$this->assertDatabaseHas('desk_cases', ['id' => $this->caseId, 'version' => 2]);
|
|
}
|
|
|
|
public function test_exact_retry_is_idempotent_and_old_close_retry_does_not_reverse_a_later_reopening(): void
|
|
{
|
|
$close = $this->operation('closed', 1);
|
|
$this->change($close)->assertOk();
|
|
$this->change([...$close, 'version' => '1'])->assertOk()->assertJsonPath('version', 2)->assertJsonPath('replayed', true);
|
|
$this->change([...$close, 'reason' => 'SYNTHETIC changed operation'])->assertConflict();
|
|
$this->change($this->operation('open', 2))->assertOk();
|
|
$this->change($close)->assertOk()->assertJsonPath('version', 3)->assertJsonPath('closed_at', null)->assertJsonPath('replayed', true);
|
|
$this->assertSame(2, DB::table('desk_events')->where('action', 'case_state_changed')->count());
|
|
}
|
|
|
|
public function test_stale_version_missing_reason_or_same_state_cannot_change_the_dossier(): void
|
|
{
|
|
$this->change($this->operation('open', 1))->assertConflict();
|
|
$this->change([...$this->operation('closed', 1), 'reason' => ''])->assertUnprocessable()->assertJsonValidationErrors('reason');
|
|
$this->postJson('/balcao-virtual/dossiers/'.$this->caseId.'/etapas', ['version' => 1, 'stage' => 'E0', 'status' => 'preparing', 'reason' => 'SYNTHETIC intervening change'])->assertOk();
|
|
$this->change($this->operation('closed', 1))->assertConflict();
|
|
$this->assertDatabaseHas('desk_cases', ['id' => $this->caseId, 'closed_at' => null, 'version' => 2]);
|
|
}
|
|
|
|
public function test_reopening_does_not_reopen_a_cancelled_request_and_records_owner_reason_and_summary(): void
|
|
{
|
|
$this->postJson('/balcao-virtual/pedidos/'.$this->requestId.'/estado', ['version' => 1, 'status' => 'cancelled', 'reason' => 'SYNTHETIC request cancellation'])->assertOk();
|
|
$this->change($this->operation('closed', 1))->assertOk();
|
|
$this->change($this->operation('open', 2))->assertOk();
|
|
$this->getJson('/balcao-virtual/pedidos/'.$this->requestId)->assertOk()->assertJsonPath('request.status', 'cancelled')->assertJsonPath('operationsEditable', false);
|
|
$event = DB::table('desk_events')->where('case_id', $this->caseId)->where('action', 'case_state_changed')->orderBy('event_order')->first();
|
|
$data = app(VirtualDesk::class)->decode($event->payload);
|
|
$this->assertSame($this->owner->id, (int) $event->actor_id);
|
|
$this->assertSame('SYNTHETIC owner decision closed', $data['reason']);
|
|
$this->assertSame(0, $data['activity']['open_requests']);
|
|
$this->assertNull($data['before']['closed_at']);
|
|
$this->assertNotNull($data['after']['closed_at']);
|
|
}
|
|
|
|
public function test_unknown_optional_task_count_is_not_reported_as_zero(): void
|
|
{
|
|
DB::statement('ALTER TABLE tasks RENAME TO tasks_qa_unavailable');
|
|
try {
|
|
$activity = app(VirtualDesk::class)->caseActivity(DB::table('desk_cases')->where('id', $this->caseId)->first());
|
|
$this->assertNull($activity['open_tasks']);
|
|
$this->assertSame(1, $activity['open_requests']);
|
|
} finally {
|
|
DB::statement('ALTER TABLE tasks_qa_unavailable RENAME TO tasks');
|
|
}
|
|
}
|
|
}
|